@entifix/react-integration 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,11 +12,12 @@ pnpm add @entifix/react-integration @tanstack/react-form @tanstack/react-query e
12
12
 
13
13
  ## Entry points
14
14
 
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 |
19
- | `@entifix/react-integration/event-source` | `makeEventSourceReactiveChannel`, the browser transport behind `ReactiveChannelTag` — imported by the composition root that picks it, never by a component |
15
+ | Import | What it holds |
16
+ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
17
+ | `@entifix/react-integration` | `EntifixQueryProvider`, `useEntityRecord`, `useEntityForm`, `useEntityMutation`, `useWizard`, `useReactiveInvalidation`, `useTransactionSettlement` |
18
+ | `@entifix/react-integration/url-query` | `defineUrlQuery`, `anyOf`, `containing`, `inAnyYear`: the query string read as a load request (filter, sort and page), with no React — for a server component parsing `searchParams` |
19
+ | `@entifix/react-integration/url` | `useUrlState`, `useEntityLoad`, `useUrlFilter`: a filter kept in the query string, answered by `loadAllUCFactory` over an `EntitySource`. No TanStack |
20
+ | `@entifix/react-integration/event-source` | `makeEventSourceReactiveChannel`, the browser transport behind `ReactiveChannelTag` — imported by the composition root that picks it, never by a component |
20
21
 
21
22
  ---
22
23
 
@@ -1,4 +1,4 @@
1
- import type { Entity, EntityFilter, EntityLoadRequest, EntitySorting, FilterGroup } from '@entifix/core';
1
+ import { type Entity, type EntityFilter, type EntityLoadRequest, type EntitySorting, type FilterGroup } from '@entifix/core';
2
2
  /**
3
3
  * Any entity, as far as a parameter can tell: it names its property by text,
4
4
  * since a dotted path (`title.en`) is no key of the entity's type.
@@ -33,7 +33,20 @@ export interface SelectParam {
33
33
  readonly select: true;
34
34
  readonly allowed?: readonly string[];
35
35
  }
36
- export type UrlParam<TContext> = FilterParam<TContext> | SortParam | SelectParam;
36
+ /**
37
+ * A parameter that picks the page of the answer: `?page=3`, `pageSize` records
38
+ * at a time. It holds one positive integer, and page 1 is written as no value
39
+ * at all, so `?page=1` and the bare URL are one address rather than two
40
+ * cached copies of the same answer.
41
+ *
42
+ * A page belongs to one answer. Changing a filter or the sort changes the
43
+ * answer, so {@link UrlQuery.withParam} and {@link UrlQuery.withoutFilters}
44
+ * drop it: page 4 of a narrower result is usually past its end.
45
+ */
46
+ export interface PageParam {
47
+ readonly pageSize: number;
48
+ }
49
+ export type UrlParam<TContext> = FilterParam<TContext> | SortParam | SelectParam | PageParam;
37
50
  /** The values of every parameter, in the order the URL gave them. */
38
51
  export type UrlState<TKey extends string> = Readonly<Record<TKey, readonly string[]>>;
39
52
  export interface UrlQuery<TKey extends string, TContext> {
@@ -46,13 +59,25 @@ export interface UrlQuery<TKey extends string, TContext> {
46
59
  /** Whether any parameter that filters has a value; a sort alone does not. */
47
60
  isFiltering(state: UrlState<TKey>): boolean;
48
61
  /**
49
- * Whether the state asks the load anything: a filter or a sort. A
62
+ * Whether the state asks the load anything: a filter, a sort or a page. A
50
63
  * selection alone does not.
51
64
  */
52
65
  isAsking(state: UrlState<TKey>): boolean;
53
- /** The state with every filter emptied, and its sort and selection kept. */
66
+ /**
67
+ * The state with every filter emptied, its sort and selection kept, and its
68
+ * page dropped.
69
+ */
54
70
  withoutFilters(state: UrlState<TKey>): UrlState<TKey>;
55
- /** The load request the state stands for: one condition per parameter set. */
71
+ /**
72
+ * The state with one parameter set to `values`, checked the way `parse`
73
+ * checks them. Setting a filter or the sort also drops the page, because
74
+ * it changes the answer the page was a page of.
75
+ */
76
+ withParam(state: UrlState<TKey>, key: TKey, values: readonly string[]): UrlState<TKey>;
77
+ /**
78
+ * The load request the state stands for: one condition per parameter set,
79
+ * the sort, and — when a page parameter is declared — `page` and `pageSize`.
80
+ */
56
81
  request<TEntity extends Entity>(state: UrlState<TKey>, context: TContext): EntityLoadRequest<TEntity>;
57
82
  }
58
83
  export interface UrlQueryOptions<TKey extends string, TContext> {
@@ -1 +1 @@
1
- {"version":3,"file":"url-query.d.ts","sourceRoot":"","sources":["../../src/url/url-query.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"url-query.d.ts","sourceRoot":"","sources":["../../src/url/url-query.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,12 @@
1
1
  import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { EntifixLogicError } from '@entifix/core';
2
3
  /** Whether a parameter orders the answer rather than filters it. */ function isSortParam(param) {
3
4
  return 'sorting' in param;
4
5
  }
6
+ /** Whether a parameter picks the page of the answer. */ function isPageParam(param) {
7
+ return 'pageSize' in param;
8
+ }
9
+ /** A page number as a URL writes it: a positive integer, without a sign or zeros. */ const PAGE_NUMBER = /^[1-9]\d*$/;
5
10
  /** Whether a parameter picks a record rather than asks the load anything. */ function isSelectParam(param) {
6
11
  return 'select' in param;
7
12
  }
@@ -14,8 +19,23 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
19
  * every value is checked, each becoming one condition of the request.
15
20
  */ export function defineUrlQuery({ params, sorting: defaultSorting }) {
16
21
  const keys = Object.keys(params);
17
- const filterKeys = keys.filter((key)=>!isSortParam(params[key]) && !isSelectParam(params[key]));
22
+ const filterKeys = keys.filter((key)=>!isSortParam(params[key]) && !isSelectParam(params[key]) && !isPageParam(params[key]));
18
23
  const askingKeys = keys.filter((key)=>!isSelectParam(params[key]));
24
+ const pageKeys = keys.filter((key)=>isPageParam(params[key]));
25
+ // Two would be two answers to "which page", and the request has one.
26
+ if (pageKeys.length > 1) {
27
+ throw new EntifixLogicError(`A URL query declares one page parameter at most; found ${pageKeys.join(', ')}.`, undefined, {
28
+ pageKeys
29
+ });
30
+ }
31
+ const [pageKey] = pageKeys;
32
+ const pageSize = pageKey === undefined ? undefined : params[pageKey].pageSize;
33
+ if (pageSize !== undefined && !(Number.isInteger(pageSize) && pageSize > 0)) {
34
+ throw new EntifixLogicError(`The page parameter "${pageKey}" needs a positive integer pageSize; got ${pageSize}.`, undefined, {
35
+ pageKey,
36
+ pageSize
37
+ });
38
+ }
19
39
  const stateOf = (values)=>Object.fromEntries(keys.map((key)=>[
20
40
  key,
21
41
  values(key)
@@ -23,6 +43,14 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
23
43
  const empty = stateOf(()=>[]);
24
44
  const accepted = (key, values)=>{
25
45
  const param = params[key];
46
+ if (isPageParam(param)) {
47
+ // Page 1 is the bare URL, so it is dropped rather than kept as a second
48
+ // spelling of the same address.
49
+ const [first] = values.filter((value)=>PAGE_NUMBER.test(value));
50
+ return first === undefined || first === '1' ? [] : [
51
+ first
52
+ ];
53
+ }
26
54
  const { allowed, single } = isSortParam(param) ? {
27
55
  allowed: Object.keys(param.sorting),
28
56
  single: true
@@ -61,7 +89,11 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
61
89
  return askingKeys.some((key)=>state[key].length > 0);
62
90
  },
63
91
  withoutFilters (state) {
64
- return stateOf((key)=>filterKeys.includes(key) ? [] : state[key]);
92
+ return stateOf((key)=>filterKeys.includes(key) || key === pageKey ? [] : state[key]);
93
+ },
94
+ withParam (state, key, values) {
95
+ const resetsPage = key !== pageKey && !isSelectParam(params[key]);
96
+ return stateOf((other)=>other === key ? accepted(key, values) : other === pageKey && resetsPage ? [] : state[other]);
65
97
  },
66
98
  request (state, context) {
67
99
  var _chosen_;
@@ -79,7 +111,11 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
79
111
  ] : [];
80
112
  });
81
113
  const sorting = (_chosen_ = chosen[0]) != null ? _chosen_ : defaultSorting;
82
- return _extends({}, filtering.length > 0 && {
114
+ const [page] = pageKey === undefined ? [] : state[pageKey];
115
+ return _extends({}, pageSize !== undefined && {
116
+ page: page === undefined ? 1 : Number(page),
117
+ pageSize
118
+ }, filtering.length > 0 && {
83
119
  filtering: filtering
84
120
  }, sorting !== undefined && {
85
121
  sorting: sorting
@@ -1 +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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"}
1
+ {"version":3,"sources":["../../src/url/url-query.ts"],"sourcesContent":["import {\n EntifixLogicError,\n type Entity,\n type EntityFilter,\n type EntityFiltering,\n type EntityLoadRequest,\n type EntitySorting,\n type 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\n/**\n * A parameter that picks the page of the answer: `?page=3`, `pageSize` records\n * at a time. It holds one positive integer, and page 1 is written as no value\n * at all, so `?page=1` and the bare URL are one address rather than two\n * cached copies of the same answer.\n *\n * A page belongs to one answer. Changing a filter or the sort changes the\n * answer, so {@link UrlQuery.withParam} and {@link UrlQuery.withoutFilters}\n * drop it: page 4 of a narrower result is usually past its end.\n */\nexport interface PageParam {\n readonly pageSize: number;\n}\n\nexport type UrlParam<TContext> =\n FilterParam<TContext> | SortParam | SelectParam | PageParam;\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 the page of the answer. */\nfunction isPageParam<TContext>(param: UrlParam<TContext>): param is PageParam {\n return 'pageSize' in param;\n}\n\n/** A page number as a URL writes it: a positive integer, without a sign or zeros. */\nconst PAGE_NUMBER = /^[1-9]\\d*$/;\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, a sort or a page. A\n * selection alone does not.\n */\n isAsking(state: UrlState<TKey>): boolean;\n /**\n * The state with every filter emptied, its sort and selection kept, and its\n * page dropped.\n */\n withoutFilters(state: UrlState<TKey>): UrlState<TKey>;\n /**\n * The state with one parameter set to `values`, checked the way `parse`\n * checks them. Setting a filter or the sort also drops the page, because\n * it changes the answer the page was a page of.\n */\n withParam(\n state: UrlState<TKey>,\n key: TKey,\n values: readonly string[],\n ): UrlState<TKey>;\n /**\n * The load request the state stands for: one condition per parameter set,\n * the sort, and — when a page parameter is declared — `page` and `pageSize`.\n */\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 =>\n !isSortParam(params[key]) &&\n !isSelectParam(params[key]) &&\n !isPageParam(params[key]),\n );\n const askingKeys = keys.filter(key => !isSelectParam(params[key]));\n const pageKeys = keys.filter(key => isPageParam(params[key]));\n // Two would be two answers to \"which page\", and the request has one.\n if (pageKeys.length > 1) {\n throw new EntifixLogicError(\n `A URL query declares one page parameter at most; found ${pageKeys.join(', ')}.`,\n undefined,\n { pageKeys },\n );\n }\n const [pageKey] = pageKeys;\n const pageSize =\n pageKey === undefined ? undefined : (params[pageKey] as PageParam).pageSize;\n if (pageSize !== undefined && !(Number.isInteger(pageSize) && pageSize > 0)) {\n throw new EntifixLogicError(\n `The page parameter \"${pageKey}\" needs a positive integer pageSize; got ${pageSize}.`,\n undefined,\n { pageKey, pageSize },\n );\n }\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: readonly string[]) => {\n const param = params[key];\n if (isPageParam(param)) {\n // Page 1 is the bare URL, so it is dropped rather than kept as a second\n // spelling of the same address.\n const [first] = values.filter(value => PAGE_NUMBER.test(value));\n return first === undefined || first === '1' ? [] : [first];\n }\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 =>\n filterKeys.includes(key) || key === pageKey ? [] : state[key],\n );\n },\n withParam(state, key, values) {\n const resetsPage = key !== pageKey && !isSelectParam(params[key]);\n return stateOf(other =>\n other === key\n ? accepted(key, values)\n : other === pageKey && resetsPage\n ? []\n : state[other],\n );\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 const [page] = pageKey === undefined ? [] : state[pageKey];\n return {\n ...(pageSize !== undefined && {\n page: page === undefined ? 1 : Number(page),\n pageSize,\n }),\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":["EntifixLogicError","isSortParam","param","isPageParam","PAGE_NUMBER","isSelectParam","defineUrlQuery","params","sorting","defaultSorting","keys","Object","filterKeys","filter","key","askingKeys","pageKeys","length","join","undefined","pageKey","pageSize","Number","isInteger","stateOf","values","fromEntries","map","empty","accepted","first","value","test","allowed","single","kept","trim","includes","unique","Set","slice","parse","search","query","URLSearchParams","getAll","serialize","state","append","text","toString","isEmpty","every","isFiltering","some","isAsking","withoutFilters","withParam","resetsPage","other","request","context","chosen","filtering","flatMap","condition","page","anyOf","property","operator","containing","inAnyYear","year","start","Date","end"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The query string as a load request, with no React behind it.
3
+ *
4
+ * Apart from `./url` because a server component that parses `searchParams`
5
+ * has no use for the hooks, and `./url` re-exports modules that import React.
6
+ * Nothing here imports anything but `@entifix/core`.
7
+ */
8
+ export * from './url/url-query.js';
9
+ //# sourceMappingURL=url-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-query.d.ts","sourceRoot":"","sources":["../src/url-query.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The query string as a load request, with no React behind it.
3
+ *
4
+ * Apart from `./url` because a server component that parses `searchParams`
5
+ * has no use for the hooks, and `./url` re-exports modules that import React.
6
+ * Nothing here imports anything but `@entifix/core`.
7
+ */ export * from './url/url-query.js';
8
+
9
+ //# sourceMappingURL=url-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/url-query.ts"],"sourcesContent":["/**\n * The query string as a load request, with no React behind it.\n *\n * Apart from `./url` because a server component that parses `searchParams`\n * has no use for the hooks, and `./url` re-exports modules that import React.\n * Nothing here imports anything but `@entifix/core`.\n */\nexport * from './url/url-query.js';\n"],"names":[],"mappings":"AAAA;;;;;;CAMC,GACD,cAAc,qBAAqB"}
package/dist/url.d.ts CHANGED
@@ -2,10 +2,11 @@
2
2
  * A filter kept in the URL and answered by the `loadAll` use case, apart from
3
3
  * the main entry: a page that only filters a list must not pull TanStack
4
4
  * Query and Form, which every hook behind `.` builds on.
5
+ *
6
+ * The query definition itself is `./url-query`, which carries no React.
5
7
  */
6
8
  export * from './url/entity-source.js';
7
9
  export * from './url/url-filter.js';
8
- export * from './url/url-query.js';
9
10
  export * from './url/url-state.js';
10
11
  export * from './url/use-entity-load.js';
11
12
  //# sourceMappingURL=url.d.ts.map
package/dist/url.d.ts.map CHANGED
@@ -1 +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"}
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC"}
package/dist/url.js CHANGED
@@ -2,9 +2,10 @@
2
2
  * A filter kept in the URL and answered by the `loadAll` use case, apart from
3
3
  * the main entry: a page that only filters a list must not pull TanStack
4
4
  * Query and Form, which every hook behind `.` builds on.
5
+ *
6
+ * The query definition itself is `./url-query`, which carries no React.
5
7
  */ export * from './url/entity-source.js';
6
8
  export * from './url/url-filter.js';
7
- export * from './url/url-query.js';
8
9
  export * from './url/url-state.js';
9
10
  export * from './url/use-entity-load.js';
10
11
 
package/dist/url.js.map CHANGED
@@ -1 +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"}
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 *\n * The query definition itself is `./url-query`, which carries no React.\n */\nexport * from './url/entity-source.js';\nexport * from './url/url-filter.js';\nexport * from './url/url-state.js';\nexport * from './url/use-entity-load.js';\n"],"names":[],"mappings":"AAAA;;;;;;CAMC,GACD,cAAc,yBAAyB;AACvC,cAAc,sBAAsB;AACpC,cAAc,qBAAqB;AACnC,cAAc,2BAA2B"}
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  }
22
22
  }
23
23
  },
24
- "version": "0.6.0",
24
+ "version": "0.7.1",
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": {
@@ -48,6 +48,11 @@
48
48
  "import": "./dist/url.js",
49
49
  "default": "./dist/url.js"
50
50
  },
51
+ "./url-query": {
52
+ "types": "./dist/url-query.d.ts",
53
+ "import": "./dist/url-query.js",
54
+ "default": "./dist/url-query.js"
55
+ },
51
56
  "./event-source": {
52
57
  "types": "./dist/event-source.d.ts",
53
58
  "import": "./dist/event-source.js",
@@ -60,16 +65,16 @@
60
65
  "LICENSE"
61
66
  ],
62
67
  "dependencies": {
63
- "@entifix/business": "0.6.0",
64
- "@entifix/core": "0.6.0",
65
- "@entifix/transactions": "0.6.0"
68
+ "@entifix/business": "0.7.1",
69
+ "@entifix/core": "0.7.1",
70
+ "@entifix/transactions": "0.7.1"
66
71
  },
67
72
  "devDependencies": {
68
73
  "@tanstack/react-form": "1.33.5",
69
74
  "@tanstack/react-query": "^5.104.5",
70
75
  "effect": "4.0.5",
71
76
  "react": "19.3.1",
72
- "@entifix/testing-unit": "0.6.0"
77
+ "@entifix/testing-unit": "0.7.1"
73
78
  },
74
79
  "peerDependencies": {
75
80
  "@tanstack/react-form": ">=1.33 <2",