@aglyn/plugins-data 1.0.0-beta.195 → 1.0.0-beta.197

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-data",
3
- "version": "1.0.0-beta.195",
3
+ "version": "1.0.0-beta.197",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,14 +25,14 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.195",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.195",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.195",
31
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.195",
32
- "@aglyn/shared-util-http": "1.0.0-beta.195",
33
- "@aglyn/shared-util-timestamp": "1.0.0-beta.195",
34
- "@aglyn/tenant-feature-instance": "1.0.0-beta.195",
35
- "@aglyn/tenant-runtime": "1.0.0-beta.195",
28
+ "@aglyn/aglyn": "1.0.0-beta.197",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.197",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.197",
31
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.197",
32
+ "@aglyn/shared-util-http": "1.0.0-beta.197",
33
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.197",
34
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.197",
35
+ "@aglyn/tenant-runtime": "1.0.0-beta.197",
36
36
  "@swc/helpers": "0.5.23"
37
37
  },
38
38
  "peerDependencies": {
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type DatasetModel } from '@aglyn/aglyn';
18
+ import type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter';
19
+ import { type ListFilterClause, type ListFilterOption } from '@aglyn/shared-ui-jsx/const/list-grid-filter';
20
+ /**
21
+ * How many records a filter the query cannot serve on its own is matched
22
+ * over. Past it the card says the window was full; a single clause or a
23
+ * single search word reaches every record without one.
24
+ */
25
+ export declare const RECORD_FILTER_WINDOW = 1000;
26
+ /**
27
+ * The grid column a dataset field is shown in. Prefixed, because field ids
28
+ * are the dataset author's and may be any name, `actions` included.
29
+ */
30
+ export declare const recordColumn: (fieldId: string) => string;
31
+ /** What the records grid's Filters panel offers for one dataset model. */
32
+ export interface DatasetRecordFilter {
33
+ fields: ListFilterField[];
34
+ options: Record<string, ListFilterOption[]>;
35
+ headers: Record<string, string>;
36
+ selectFields: string[];
37
+ }
38
+ /**
39
+ * The model's fields as filter fields, each reading the record row at
40
+ * `values.<fieldId>`: an enum is a select over its options, a boolean a
41
+ * true/false select, numbers and timestamps number and date fields, a list a
42
+ * member match, and plain text a text field. References, maps, bytes,
43
+ * coordinates and nil fields offer no filter.
44
+ */
45
+ export declare function datasetRecordFilter(model: DatasetModel): DatasetRecordFilter;
46
+ /** Which condition the query serves, and what is left to match in memory. */
47
+ export interface RecordFilterPlan {
48
+ /** The `filterKeys` token the query narrows by, or null for the plain walk. */
49
+ token: string | null;
50
+ /** The column of the served clause; null when a search word is served. */
51
+ servedField: string | null;
52
+ /** Whether anything is left for the window to match. */
53
+ windowed: boolean;
54
+ /** Every token each token-served clause needs, by clause index. */
55
+ clauseTokens: Array<string[] | null>;
56
+ /** Every quick-search token, one per word. */
57
+ searchTokens: string[];
58
+ clauses: readonly ListFilterClause[];
59
+ }
60
+ /**
61
+ * The served condition is the first clause a token answers, else the first
62
+ * search word; the walk is windowed whenever anything else is in force.
63
+ */
64
+ export declare function planRecordFilter(model: DatasetModel, clauses: readonly ListFilterClause[], searchWords: readonly string[]): RecordFilterPlan;
65
+ /**
66
+ * The window's records that answer every condition.
67
+ *
68
+ * A token-served clause and the quick search are matched by the SAME tokens
69
+ * the query uses, computed from the record's values under the current model,
70
+ * so a record answers in memory exactly when it would answer on the server —
71
+ * a text `contains` is a word-prefix match either way. Everything else
72
+ * (ranges, dates, empty checks, negations, `isAnyOf`) is matched by the
73
+ * shared grammar over `values.<fieldId>`.
74
+ */
75
+ export declare function matchRecordWindow<Row extends {
76
+ values?: Record<string, unknown>;
77
+ }>(model: DatasetModel, fields: readonly ListFilterField[], rows: readonly Row[], plan: RecordFilterPlan): Row[];
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { datasetFilterKeys, datasetFilterToken, datasetFilterTokens, datasetFilterWords, datasetSearchToken } from "@aglyn/aglyn";
17
+ import { filterListRows, inMemoryListField } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
18
+ /**
19
+ * How many records a filter the query cannot serve on its own is matched
20
+ * over. Past it the card says the window was full; a single clause or a
21
+ * single search word reaches every record without one.
22
+ */ export const RECORD_FILTER_WINDOW = 1000;
23
+ /**
24
+ * The grid column a dataset field is shown in. Prefixed, because field ids
25
+ * are the dataset author's and may be any name, `actions` included.
26
+ */ export const recordColumn = (fieldId)=>`values.${fieldId}`;
27
+ const COLUMN_PREFIX = recordColumn('');
28
+ const fieldIdOf = (column)=>column.startsWith(COLUMN_PREFIX) ? column.slice(COLUMN_PREFIX.length) : column;
29
+ const BOOLEAN_OPTIONS = [
30
+ {
31
+ value: 'true',
32
+ label: 'True'
33
+ },
34
+ {
35
+ value: 'false',
36
+ label: 'False'
37
+ }
38
+ ];
39
+ /**
40
+ * The model's fields as filter fields, each reading the record row at
41
+ * `values.<fieldId>`: an enum is a select over its options, a boolean a
42
+ * true/false select, numbers and timestamps number and date fields, a list a
43
+ * member match, and plain text a text field. References, maps, bytes,
44
+ * coordinates and nil fields offer no filter.
45
+ */ export function datasetRecordFilter(model) {
46
+ var _model_order;
47
+ const fields = [];
48
+ const options = {};
49
+ const headers = {};
50
+ const selectFields = [];
51
+ for (const fieldId of (_model_order = model.order) != null ? _model_order : []){
52
+ var _ref;
53
+ var _model_fields, _field_validation;
54
+ const field = (_model_fields = model.fields) == null ? void 0 : _model_fields[fieldId];
55
+ if (!field) continue;
56
+ const column = recordColumn(fieldId);
57
+ const choices = field.type === 'text' ? (_ref = (_field_validation = field.validation) == null ? void 0 : _field_validation.options) != null ? _ref : [] : [];
58
+ let declared = null;
59
+ if (field.type === 'text' && choices.length) {
60
+ declared = inMemoryListField(column, 'select');
61
+ options[column] = choices.map((choice)=>({
62
+ value: choice,
63
+ label: choice
64
+ }));
65
+ } else if (field.type === 'text') {
66
+ declared = inMemoryListField(column, 'text');
67
+ } else if (field.type === 'bool') {
68
+ declared = inMemoryListField(column, 'select');
69
+ options[column] = BOOLEAN_OPTIONS;
70
+ } else if (field.type === 'int32' || field.type === 'int64' || field.type === 'float') {
71
+ declared = inMemoryListField(column, 'number');
72
+ } else if (field.type === 'timestamp') {
73
+ declared = inMemoryListField(column, 'date');
74
+ } else if (field.type === 'sorted') {
75
+ // A list matches by whole member, as its `=` tokens do.
76
+ declared = {
77
+ column,
78
+ path: column,
79
+ kind: 'text',
80
+ tokensPath: column,
81
+ verbatimTokens: true,
82
+ operators: [
83
+ 'contains',
84
+ 'isEmpty',
85
+ 'isNotEmpty'
86
+ ]
87
+ };
88
+ }
89
+ if (!declared) continue;
90
+ fields.push(declared);
91
+ headers[column] = field.name || fieldId;
92
+ if (options[column]) selectFields.push(column);
93
+ }
94
+ return {
95
+ fields,
96
+ options,
97
+ headers,
98
+ selectFields
99
+ };
100
+ }
101
+ const asFieldClause = (clause)=>({
102
+ field: fieldIdOf(clause.field),
103
+ op: clause.op,
104
+ value: clause.value
105
+ });
106
+ /**
107
+ * The served condition is the first clause a token answers, else the first
108
+ * search word; the walk is windowed whenever anything else is in force.
109
+ */ export function planRecordFilter(model, clauses, searchWords) {
110
+ var _searchTokens_;
111
+ var _clauseTokens_servedAt;
112
+ const clauseTokens = clauses.map((clause)=>datasetFilterTokens(model, asFieldClause(clause)));
113
+ const searchTokens = [
114
+ ...new Set(searchWords.flatMap((word)=>datasetFilterWords(word)).map((word)=>datasetSearchToken(word)).filter((token)=>Boolean(token)))
115
+ ];
116
+ const servedAt = clauses.findIndex((clause)=>Boolean(datasetFilterToken(model, asFieldClause(clause))));
117
+ const token = servedAt >= 0 ? datasetFilterToken(model, asFieldClause(clauses[servedAt])) : (_searchTokens_ = searchTokens[0]) != null ? _searchTokens_ : null;
118
+ const complete = servedAt >= 0 ? clauses.length === 1 && searchTokens.length === 0 && ((_clauseTokens_servedAt = clauseTokens[servedAt]) == null ? void 0 : _clauseTokens_servedAt.length) === 1 : clauses.length === 0 && searchTokens.length <= 1;
119
+ return {
120
+ token,
121
+ servedField: servedAt >= 0 ? clauses[servedAt].field : null,
122
+ windowed: !complete,
123
+ clauseTokens,
124
+ searchTokens,
125
+ clauses
126
+ };
127
+ }
128
+ /**
129
+ * The window's records that answer every condition.
130
+ *
131
+ * A token-served clause and the quick search are matched by the SAME tokens
132
+ * the query uses, computed from the record's values under the current model,
133
+ * so a record answers in memory exactly when it would answer on the server —
134
+ * a text `contains` is a word-prefix match either way. Everything else
135
+ * (ranges, dates, empty checks, negations, `isAnyOf`) is matched by the
136
+ * shared grammar over `values.<fieldId>`.
137
+ */ export function matchRecordWindow(model, fields, rows, plan) {
138
+ const served = new Set(plan.clauses.filter((_clause, at)=>plan.clauseTokens[at] !== null));
139
+ const needed = [
140
+ ...plan.clauseTokens.flatMap((tokens)=>tokens != null ? tokens : []),
141
+ ...plan.searchTokens
142
+ ];
143
+ const rest = filterListRows(rows, fields, plan.clauses, {
144
+ paths: [],
145
+ words: []
146
+ }, (clause)=>served.has(clause));
147
+ if (!needed.length) return rest;
148
+ return rest.filter((row)=>{
149
+ const keys = new Set(datasetFilterKeys(model, row.values));
150
+ return needed.every((token)=>keys.has(token));
151
+ });
152
+ }
153
+
154
+ //# sourceMappingURL=dataset-record-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/components/dataset-record-filter.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type DatasetModel,\n datasetFilterKeys,\n datasetFilterToken,\n datasetFilterTokens,\n datasetFilterWords,\n datasetSearchToken,\n} from '@aglyn/aglyn'\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport {\n filterListRows,\n inMemoryListField,\n type ListFilterClause,\n type ListFilterOption,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\n\n/**\n * How many records a filter the query cannot serve on its own is matched\n * over. Past it the card says the window was full; a single clause or a\n * single search word reaches every record without one.\n */\nexport const RECORD_FILTER_WINDOW = 1000\n\n/**\n * The grid column a dataset field is shown in. Prefixed, because field ids\n * are the dataset author's and may be any name, `actions` included.\n */\nexport const recordColumn = (fieldId: string) => `values.${fieldId}`\nconst COLUMN_PREFIX = recordColumn('')\nconst fieldIdOf = (column: string) =>\n column.startsWith(COLUMN_PREFIX) ? column.slice(COLUMN_PREFIX.length) : column\n\n/** What the records grid's Filters panel offers for one dataset model. */\nexport interface DatasetRecordFilter {\n fields: ListFilterField[]\n options: Record<string, ListFilterOption[]>\n headers: Record<string, string>\n selectFields: string[]\n}\n\nconst BOOLEAN_OPTIONS: ListFilterOption[] = [\n { value: 'true', label: 'True' },\n { value: 'false', label: 'False' },\n]\n\n/**\n * The model's fields as filter fields, each reading the record row at\n * `values.<fieldId>`: an enum is a select over its options, a boolean a\n * true/false select, numbers and timestamps number and date fields, a list a\n * member match, and plain text a text field. References, maps, bytes,\n * coordinates and nil fields offer no filter.\n */\nexport function datasetRecordFilter(model: DatasetModel): DatasetRecordFilter {\n const fields: ListFilterField[] = []\n const options: Record<string, ListFilterOption[]> = {}\n const headers: Record<string, string> = {}\n const selectFields: string[] = []\n for (const fieldId of model.order ?? []) {\n const field = model.fields?.[fieldId]\n if (!field) continue\n const column = recordColumn(fieldId)\n const choices = field.type === 'text' ? (field.validation?.options ?? []) : []\n let declared: ListFilterField | null = null\n if (field.type === 'text' && choices.length) {\n declared = inMemoryListField(column, 'select')\n options[column] = choices.map((choice) => ({ value: choice, label: choice }))\n } else if (field.type === 'text') {\n declared = inMemoryListField(column, 'text')\n } else if (field.type === 'bool') {\n declared = inMemoryListField(column, 'select')\n options[column] = BOOLEAN_OPTIONS\n } else if (field.type === 'int32' || field.type === 'int64' || field.type === 'float') {\n declared = inMemoryListField(column, 'number')\n } else if (field.type === 'timestamp') {\n declared = inMemoryListField(column, 'date')\n } else if (field.type === 'sorted') {\n // A list matches by whole member, as its `=` tokens do.\n declared = {\n column,\n path: column,\n kind: 'text',\n tokensPath: column,\n verbatimTokens: true,\n operators: ['contains', 'isEmpty', 'isNotEmpty'],\n }\n }\n if (!declared) continue\n fields.push(declared)\n headers[column] = field.name || fieldId\n if (options[column]) selectFields.push(column)\n }\n return { fields, options, headers, selectFields }\n}\n\n/** Which condition the query serves, and what is left to match in memory. */\nexport interface RecordFilterPlan {\n /** The `filterKeys` token the query narrows by, or null for the plain walk. */\n token: string | null\n /** The column of the served clause; null when a search word is served. */\n servedField: string | null\n /** Whether anything is left for the window to match. */\n windowed: boolean\n /** Every token each token-served clause needs, by clause index. */\n clauseTokens: Array<string[] | null>\n /** Every quick-search token, one per word. */\n searchTokens: string[]\n clauses: readonly ListFilterClause[]\n}\n\nconst asFieldClause = (clause: ListFilterClause) => ({\n field: fieldIdOf(clause.field),\n op: clause.op,\n value: clause.value,\n})\n\n/**\n * The served condition is the first clause a token answers, else the first\n * search word; the walk is windowed whenever anything else is in force.\n */\nexport function planRecordFilter(\n model: DatasetModel,\n clauses: readonly ListFilterClause[],\n searchWords: readonly string[],\n): RecordFilterPlan {\n const clauseTokens = clauses.map((clause) =>\n datasetFilterTokens(model, asFieldClause(clause)),\n )\n const searchTokens = [\n ...new Set(\n searchWords\n .flatMap((word) => datasetFilterWords(word))\n .map((word) => datasetSearchToken(word))\n .filter((token): token is string => Boolean(token)),\n ),\n ]\n const servedAt = clauses.findIndex((clause) =>\n Boolean(datasetFilterToken(model, asFieldClause(clause))),\n )\n const token =\n servedAt >= 0\n ? datasetFilterToken(model, asFieldClause(clauses[servedAt]))\n : (searchTokens[0] ?? null)\n const complete =\n servedAt >= 0\n ? clauses.length === 1 &&\n searchTokens.length === 0 &&\n clauseTokens[servedAt]?.length === 1\n : clauses.length === 0 && searchTokens.length <= 1\n return {\n token,\n servedField: servedAt >= 0 ? clauses[servedAt].field : null,\n windowed: !complete,\n clauseTokens,\n searchTokens,\n clauses,\n }\n}\n\n/**\n * The window's records that answer every condition.\n *\n * A token-served clause and the quick search are matched by the SAME tokens\n * the query uses, computed from the record's values under the current model,\n * so a record answers in memory exactly when it would answer on the server —\n * a text `contains` is a word-prefix match either way. Everything else\n * (ranges, dates, empty checks, negations, `isAnyOf`) is matched by the\n * shared grammar over `values.<fieldId>`.\n */\nexport function matchRecordWindow<Row extends { values?: Record<string, unknown> }>(\n model: DatasetModel,\n fields: readonly ListFilterField[],\n rows: readonly Row[],\n plan: RecordFilterPlan,\n): Row[] {\n const served = new Set(\n plan.clauses.filter((_clause, at) => plan.clauseTokens[at] !== null),\n )\n const needed = [\n ...plan.clauseTokens.flatMap((tokens) => tokens ?? []),\n ...plan.searchTokens,\n ]\n const rest = filterListRows(\n rows,\n fields,\n plan.clauses,\n { paths: [], words: [] },\n (clause) => served.has(clause),\n )\n if (!needed.length) return rest\n return rest.filter((row) => {\n const keys = new Set(datasetFilterKeys(model, row.values))\n return needed.every((token) => keys.has(token))\n })\n}\n"],"names":["datasetFilterKeys","datasetFilterToken","datasetFilterTokens","datasetFilterWords","datasetSearchToken","filterListRows","inMemoryListField","RECORD_FILTER_WINDOW","recordColumn","fieldId","COLUMN_PREFIX","fieldIdOf","column","startsWith","slice","length","BOOLEAN_OPTIONS","value","label","datasetRecordFilter","model","fields","options","headers","selectFields","order","field","choices","type","validation","declared","map","choice","path","kind","tokensPath","verbatimTokens","operators","push","name","asFieldClause","clause","op","planRecordFilter","clauses","searchWords","searchTokens","clauseTokens","Set","flatMap","word","filter","token","Boolean","servedAt","findIndex","complete","servedField","windowed","matchRecordWindow","rows","plan","served","_clause","at","needed","tokens","rest","paths","words","has","row","keys","values","every"],"mappings":"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"}