@stndrds/schema 1.0.0-alpha.192 → 1.0.0-alpha.194
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/dist/attributes-DP08iPCh.d.mts +889 -0
- package/dist/attributes-Ddhps_AU.d.ts +889 -0
- package/dist/{chunk-MAKSIK3P.js → chunk-5WATIVCA.js} +2 -2
- package/dist/{chunk-4KRLCWJM.mjs → chunk-6I2R22CX.mjs} +1 -1
- package/dist/{chunk-DC7YYE3U.js → chunk-ACKMC6FL.js} +2 -2
- package/dist/{chunk-TEQNVO7W.mjs → chunk-AYVHMVKQ.mjs} +1 -1
- package/dist/{chunk-P2BE2A2G.js → chunk-BV3IUMMW.js} +4 -7
- package/dist/{chunk-CCB4OY2O.js → chunk-E6JUYGJX.js} +2 -2
- package/dist/{chunk-7A3Y6O32.js → chunk-EBGRZUIH.js} +2 -2
- package/dist/{chunk-QWZHR2OO.js → chunk-EW5XR2X3.js} +2 -2
- package/dist/{chunk-UQVW4KPP.mjs → chunk-HRQEZUAA.mjs} +0 -1
- package/dist/{chunk-OAQWRMTP.js → chunk-HTSTPTNZ.js} +2 -2
- package/dist/{chunk-U4JC7P6D.mjs → chunk-HXDDJLHD.mjs} +37 -52
- package/dist/{chunk-Z75P63VF.mjs → chunk-JUKLL5RJ.mjs} +1 -1
- package/dist/{chunk-YQROI4IE.js → chunk-M5QIBMIH.js} +37 -52
- package/dist/{chunk-FQ2ZYOVS.js → chunk-MZOEL7FN.js} +47 -41
- package/dist/{chunk-KWVCGQ7N.mjs → chunk-NOMHDOVE.mjs} +2 -5
- package/dist/{chunk-OLJLCVNY.mjs → chunk-NS63SPNN.mjs} +1 -1
- package/dist/{chunk-J5HRK3WD.js → chunk-O44XVGHE.js} +2 -2
- package/dist/{chunk-TMYBEXBT.mjs → chunk-OSSGN43B.mjs} +1 -1
- package/dist/{chunk-LTHSU4NF.mjs → chunk-PMKCDRRO.mjs} +26 -21
- package/dist/{chunk-4TC27SMF.mjs → chunk-QEZUDVTN.mjs} +1 -1
- package/dist/{chunk-BLXQGQLK.mjs → chunk-QVLQPW3O.mjs} +1 -1
- package/dist/{chunk-EZHMVZQ4.js → chunk-QWKLQRBX.js} +4 -4
- package/dist/{chunk-ROAZLZA2.js → chunk-ROLWVTU3.js} +2 -2
- package/dist/{chunk-3JINFUN3.js → chunk-SGSGREPG.js} +2 -6
- package/dist/{chunk-VXOTYFEW.js → chunk-T6L2MUG2.js} +2 -2
- package/dist/{chunk-KNYZH2WD.mjs → chunk-UANQJ2GH.mjs} +8 -1
- package/dist/{chunk-IRTRJH37.mjs → chunk-UXAHLSWC.mjs} +1 -1
- package/dist/{chunk-ACQ3TUFK.js → chunk-V2UA2D4E.js} +2 -2
- package/dist/{chunk-R4CJHCC4.mjs → chunk-XZ5RG2OG.mjs} +1 -4
- package/dist/{chunk-TKN73433.js → chunk-YIP5FJ5H.js} +9 -2
- package/dist/{chunk-T225DB55.js → chunk-YKWSHBT5.js} +0 -1
- package/dist/{chunk-TBYXYSGA.mjs → chunk-YUFTHPOE.mjs} +1 -1
- package/dist/{chunk-ITTQ4FGR.mjs → chunk-ZDMOXF3U.mjs} +1 -1
- package/dist/{chunk-SMOHDR6M.mjs → chunk-ZDQ5AP26.mjs} +1 -1
- package/dist/{helpers-Y9rNQ6sz.d.ts → helpers-Bph8AoY7.d.mts} +15 -3
- package/dist/{helpers-HrKmSkLi.d.mts → helpers-D9klos6x.d.ts} +15 -3
- package/dist/index.d.mts +1121 -235
- package/dist/index.d.ts +1121 -235
- package/dist/index.js +2369 -854
- package/dist/index.mjs +2264 -774
- package/dist/{types-Bdlhgrf7.d.ts → types-D38kriFw.d.mts} +1 -2
- package/dist/{types-C5DittlR.d.mts → types-iKvYBe6v.d.ts} +1 -2
- package/dist/validation/all.d.mts +3 -3
- package/dist/validation/all.d.ts +3 -3
- package/dist/validation/all.js +30 -27
- package/dist/validation/all.mjs +17 -18
- package/dist/validation/complex/currency.d.mts +2 -2
- package/dist/validation/complex/currency.d.ts +2 -2
- package/dist/validation/complex/currency.js +3 -3
- package/dist/validation/complex/currency.mjs +2 -2
- package/dist/validation/complex/file.d.mts +2 -2
- package/dist/validation/complex/file.d.ts +2 -2
- package/dist/validation/complex/file.js +3 -3
- package/dist/validation/complex/file.mjs +2 -2
- package/dist/validation/complex/location.d.mts +2 -2
- package/dist/validation/complex/location.d.ts +2 -2
- package/dist/validation/complex/location.js +3 -3
- package/dist/validation/complex/location.mjs +2 -2
- package/dist/validation/complex/phone.d.mts +2 -2
- package/dist/validation/complex/phone.d.ts +2 -2
- package/dist/validation/complex/phone.js +3 -3
- package/dist/validation/complex/phone.mjs +2 -2
- package/dist/validation/complex/relation.d.mts +2 -2
- package/dist/validation/complex/relation.d.ts +2 -2
- package/dist/validation/complex/relation.js +5 -5
- package/dist/validation/complex/relation.mjs +2 -2
- package/dist/validation/complex/richtext.d.mts +2 -2
- package/dist/validation/complex/richtext.d.ts +2 -2
- package/dist/validation/complex/richtext.js +3 -3
- package/dist/validation/complex/richtext.mjs +2 -2
- package/dist/validation/complex/select.d.mts +2 -2
- package/dist/validation/complex/select.d.ts +2 -2
- package/dist/validation/complex/select.js +5 -5
- package/dist/validation/complex/select.mjs +2 -2
- package/dist/validation/complex/user.d.mts +2 -2
- package/dist/validation/complex/user.d.ts +2 -2
- package/dist/validation/complex/user.js +3 -3
- package/dist/validation/complex/user.mjs +2 -2
- package/dist/validation/computed/formula.d.mts +2 -2
- package/dist/validation/computed/formula.d.ts +2 -2
- package/dist/validation/computed/formula.js +3 -3
- package/dist/validation/computed/formula.mjs +2 -2
- package/dist/validation/computed/rollup.d.mts +2 -2
- package/dist/validation/computed/rollup.d.ts +2 -2
- package/dist/validation/computed/rollup.js +3 -3
- package/dist/validation/computed/rollup.mjs +2 -2
- package/dist/validation/config/index.d.mts +1 -1
- package/dist/validation/config/index.d.ts +1 -1
- package/dist/validation/config/index.js +4 -4
- package/dist/validation/config/index.mjs +1 -1
- package/dist/validation/core/index.d.mts +3 -3
- package/dist/validation/core/index.d.ts +3 -3
- package/dist/validation/core/index.js +3 -3
- package/dist/validation/core/index.mjs +1 -1
- package/dist/validation/object/index.d.mts +3 -3
- package/dist/validation/object/index.d.ts +3 -3
- package/dist/validation/object/index.js +32 -29
- package/dist/validation/object/index.mjs +16 -17
- package/dist/validation/primitives/checkbox.d.mts +2 -2
- package/dist/validation/primitives/checkbox.d.ts +2 -2
- package/dist/validation/primitives/checkbox.js +3 -3
- package/dist/validation/primitives/checkbox.mjs +2 -2
- package/dist/validation/primitives/date.d.mts +2 -2
- package/dist/validation/primitives/date.d.ts +2 -2
- package/dist/validation/primitives/date.js +3 -3
- package/dist/validation/primitives/date.mjs +2 -2
- package/dist/validation/primitives/number.d.mts +2 -2
- package/dist/validation/primitives/number.d.ts +2 -2
- package/dist/validation/primitives/number.js +3 -3
- package/dist/validation/primitives/number.mjs +2 -2
- package/dist/validation/primitives/text.d.mts +3 -8
- package/dist/validation/primitives/text.d.ts +3 -8
- package/dist/validation/primitives/text.js +3 -7
- package/dist/validation/primitives/text.mjs +2 -2
- package/package.json +2 -13
- package/dist/chunk-D7WFIGXW.js +0 -14
- package/dist/chunk-RUYUFXNW.mjs +0 -12
- package/dist/filters-DVBn5tov.d.ts +0 -1630
- package/dist/filters-HDG4kLoo.d.mts +0 -1630
- package/dist/validation/primitives/rating.d.mts +0 -12
- package/dist/validation/primitives/rating.d.ts +0 -12
- package/dist/validation/primitives/rating.js +0 -11
- package/dist/validation/primitives/rating.mjs +0 -2
package/dist/index.mjs
CHANGED
|
@@ -1,34 +1,31 @@
|
|
|
1
1
|
import { generateId } from './chunk-QY6QFRRV.mjs';
|
|
2
2
|
export { asTenantId, asUserId, deepEqual, generateId, indexBy } from './chunk-QY6QFRRV.mjs';
|
|
3
3
|
import './chunk-SP3PNHYF.mjs';
|
|
4
|
-
export { parseAttributeConfig } from './chunk-
|
|
5
|
-
import { createObjectValidator, SchemaError, SchemaErrorCode, ValidationError, createAttributeValidator } from './chunk-
|
|
6
|
-
export { AccessDeniedError, AttributeInUseError, AttributeNotFoundError, ChangeTypeNotSupportedError, ConcurrentModificationError, DestructiveSyncNotAllowedError, DuplicateError, ForbiddenError, MemoryNotFoundError, MigrationTimeoutError, NotFoundError, NotImplementedError, ObjectNotFoundError, ObjectReferencedError, OrphanSystemAttributeError, ProtectedResourceError, ProtectedRoleError, RecordNotFoundError, RecordReferencedError, RepositoryError, RoleNotFoundError, SchemaError, SchemaErrorCode, StorageError, SyncCascadeError, SyncConflictError, SyncError, SystemEntityImmutableError, ValidationError, computeRecordStatus, createFormAttributeValidator, isAttributeInUseError, isNotFoundError, isObjectReferencedError, isProtectedResourceError, isRecordReferencedError, isSchemaError, isValidationError, validateDraft, validateDraftOrThrow, validateObject, validateObjectOrThrow } from './chunk-
|
|
7
|
-
import './chunk-
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
10
|
-
import './chunk-
|
|
11
|
-
import './chunk-
|
|
12
|
-
import './chunk-
|
|
13
|
-
import './chunk-
|
|
14
|
-
import './chunk-
|
|
15
|
-
import './chunk-
|
|
16
|
-
import './chunk-
|
|
17
|
-
import './chunk-
|
|
18
|
-
import './chunk-
|
|
19
|
-
import './chunk-
|
|
20
|
-
import
|
|
21
|
-
|
|
22
|
-
export { DEFAULT_VALIDATION_MESSAGES, formatZodErrors } from './chunk-UQVW4KPP.mjs';
|
|
4
|
+
export { parseAttributeConfig } from './chunk-HXDDJLHD.mjs';
|
|
5
|
+
import { createObjectValidator, SchemaError, SchemaErrorCode, ValidationError, createAttributeValidator } from './chunk-PMKCDRRO.mjs';
|
|
6
|
+
export { AccessDeniedError, AttributeInUseError, AttributeNotFoundError, ChangeTypeNotSupportedError, ConcurrentModificationError, DestructiveSyncNotAllowedError, DuplicateError, ForbiddenError, MemoryNotFoundError, MigrationTimeoutError, NotFoundError, NotImplementedError, ObjectNotFoundError, ObjectReferencedError, OrphanSystemAttributeError, ProtectedResourceError, ProtectedRoleError, RecordNotFoundError, RecordReferencedError, RepositoryError, RoleNotFoundError, SchemaError, SchemaErrorCode, StorageError, SyncCascadeError, SyncConflictError, SyncError, SystemEntityImmutableError, ValidationError, computeRecordStatus, createFormAttributeValidator, isAttributeInUseError, isNotFoundError, isObjectReferencedError, isProtectedResourceError, isRecordReferencedError, isSchemaError, isValidationError, rejectUnknownAttributesOrThrow, validateDraft, validateDraftOrThrow, validateObject, validateObjectOrThrow } from './chunk-PMKCDRRO.mjs';
|
|
7
|
+
import './chunk-OSSGN43B.mjs';
|
|
8
|
+
import './chunk-6I2R22CX.mjs';
|
|
9
|
+
import './chunk-UANQJ2GH.mjs';
|
|
10
|
+
import './chunk-NOMHDOVE.mjs';
|
|
11
|
+
import './chunk-QVLQPW3O.mjs';
|
|
12
|
+
import './chunk-ZDQ5AP26.mjs';
|
|
13
|
+
import './chunk-ZDMOXF3U.mjs';
|
|
14
|
+
import './chunk-UXAHLSWC.mjs';
|
|
15
|
+
import './chunk-NS63SPNN.mjs';
|
|
16
|
+
import './chunk-AYVHMVKQ.mjs';
|
|
17
|
+
import './chunk-QEZUDVTN.mjs';
|
|
18
|
+
import './chunk-YUFTHPOE.mjs';
|
|
19
|
+
import { formatPhoneForDisplay } from './chunk-JUKLL5RJ.mjs';
|
|
20
|
+
import './chunk-XZ5RG2OG.mjs';
|
|
21
|
+
export { DEFAULT_VALIDATION_MESSAGES, formatZodErrors } from './chunk-HRQEZUAA.mjs';
|
|
23
22
|
import { ICONS, getCountryDisplayNameByIso3 } from '@stndrds/constants';
|
|
24
23
|
import z3, { z } from 'zod';
|
|
25
|
-
import { Parser } from 'expr-eval';
|
|
26
24
|
|
|
27
25
|
// src/types/attribute-protection.ts
|
|
28
26
|
var IDENTITY_PROPERTIES = ["name", "type"];
|
|
29
27
|
var BEHAVIOR_PROPERTIES = [
|
|
30
28
|
"required",
|
|
31
|
-
"disabled",
|
|
32
29
|
"hidden",
|
|
33
30
|
"archived",
|
|
34
31
|
"deprecated",
|
|
@@ -39,6 +36,318 @@ var BEHAVIOR_PROPERTIES = [
|
|
|
39
36
|
];
|
|
40
37
|
var PRESENTATION_PROPERTIES = ["label", "description", "placeholder", "icon"];
|
|
41
38
|
|
|
39
|
+
// src/types/attribute-capabilities.ts
|
|
40
|
+
var TEXT_OPERATORS = [
|
|
41
|
+
"is",
|
|
42
|
+
"is_not",
|
|
43
|
+
"contains",
|
|
44
|
+
"not_contains",
|
|
45
|
+
"starts_with",
|
|
46
|
+
"ends_with",
|
|
47
|
+
"is_empty",
|
|
48
|
+
"is_not_empty"
|
|
49
|
+
];
|
|
50
|
+
var NUMBER_OPERATORS = [
|
|
51
|
+
"eq",
|
|
52
|
+
"neq",
|
|
53
|
+
"lt",
|
|
54
|
+
"gt",
|
|
55
|
+
"lte",
|
|
56
|
+
"gte",
|
|
57
|
+
"is_empty",
|
|
58
|
+
"is_not_empty"
|
|
59
|
+
];
|
|
60
|
+
var CHECKBOX_OPERATORS = [
|
|
61
|
+
"is_checked",
|
|
62
|
+
"is_not_checked"
|
|
63
|
+
];
|
|
64
|
+
var DATE_OPERATORS = [
|
|
65
|
+
"is",
|
|
66
|
+
"is_not",
|
|
67
|
+
"before",
|
|
68
|
+
"after",
|
|
69
|
+
"on_or_before",
|
|
70
|
+
"on_or_after",
|
|
71
|
+
"is_within",
|
|
72
|
+
"day_month_eq",
|
|
73
|
+
"is_empty",
|
|
74
|
+
"is_not_empty"
|
|
75
|
+
];
|
|
76
|
+
var SELECT_OPERATORS = [
|
|
77
|
+
"is",
|
|
78
|
+
"is_not",
|
|
79
|
+
"any_of",
|
|
80
|
+
"none_of",
|
|
81
|
+
"is_empty",
|
|
82
|
+
"is_not_empty"
|
|
83
|
+
];
|
|
84
|
+
var MULTISELECT_OPERATORS = [
|
|
85
|
+
"contains",
|
|
86
|
+
"not_contains",
|
|
87
|
+
"is_empty",
|
|
88
|
+
"is_not_empty"
|
|
89
|
+
];
|
|
90
|
+
var REFERENCE_OPERATORS = [
|
|
91
|
+
"is",
|
|
92
|
+
"is_not",
|
|
93
|
+
"any_of",
|
|
94
|
+
"none_of",
|
|
95
|
+
"contains",
|
|
96
|
+
"not_contains",
|
|
97
|
+
"is_empty",
|
|
98
|
+
"is_not_empty"
|
|
99
|
+
];
|
|
100
|
+
var PRESENCE_OPERATORS = [
|
|
101
|
+
"is_empty",
|
|
102
|
+
"is_not_empty"
|
|
103
|
+
];
|
|
104
|
+
var FORMULA_OPERATORS = [
|
|
105
|
+
"is",
|
|
106
|
+
"is_not",
|
|
107
|
+
"eq",
|
|
108
|
+
"neq",
|
|
109
|
+
"lt",
|
|
110
|
+
"gt",
|
|
111
|
+
"lte",
|
|
112
|
+
"gte",
|
|
113
|
+
"contains",
|
|
114
|
+
"not_contains",
|
|
115
|
+
"is_empty",
|
|
116
|
+
"is_not_empty"
|
|
117
|
+
];
|
|
118
|
+
var ATTRIBUTE_FILTER_OPERATORS = {
|
|
119
|
+
text: TEXT_OPERATORS,
|
|
120
|
+
richtext: [],
|
|
121
|
+
number: NUMBER_OPERATORS,
|
|
122
|
+
checkbox: CHECKBOX_OPERATORS,
|
|
123
|
+
date: DATE_OPERATORS,
|
|
124
|
+
phone: ["is", "is_not", "contains", "not_contains", "is_empty", "is_not_empty"],
|
|
125
|
+
currency: NUMBER_OPERATORS,
|
|
126
|
+
status: SELECT_OPERATORS,
|
|
127
|
+
select: SELECT_OPERATORS,
|
|
128
|
+
multiselect: MULTISELECT_OPERATORS,
|
|
129
|
+
location: [],
|
|
130
|
+
file: PRESENCE_OPERATORS,
|
|
131
|
+
user: REFERENCE_OPERATORS,
|
|
132
|
+
relation: REFERENCE_OPERATORS,
|
|
133
|
+
formula: FORMULA_OPERATORS,
|
|
134
|
+
document: PRESENCE_OPERATORS
|
|
135
|
+
};
|
|
136
|
+
var COMPUTED_TYPES = /* @__PURE__ */ new Set(["formula", "rollup"]);
|
|
137
|
+
var EDGE_BACKED_TYPES = /* @__PURE__ */ new Set(["file", "user", "relation", "document"]);
|
|
138
|
+
var DIRECT_EDIT_TYPES = /* @__PURE__ */ new Set(["checkbox"]);
|
|
139
|
+
var STATIC_DEFAULT_TYPES = /* @__PURE__ */ new Set([
|
|
140
|
+
"text",
|
|
141
|
+
"richtext",
|
|
142
|
+
"number",
|
|
143
|
+
"select",
|
|
144
|
+
"multiselect",
|
|
145
|
+
"status",
|
|
146
|
+
"date",
|
|
147
|
+
"phone",
|
|
148
|
+
"currency",
|
|
149
|
+
"location",
|
|
150
|
+
"user",
|
|
151
|
+
"file",
|
|
152
|
+
"relation",
|
|
153
|
+
"document"
|
|
154
|
+
]);
|
|
155
|
+
var PLACEHOLDER_TYPES = /* @__PURE__ */ new Set([
|
|
156
|
+
"text",
|
|
157
|
+
"number",
|
|
158
|
+
"date",
|
|
159
|
+
"phone",
|
|
160
|
+
"currency",
|
|
161
|
+
"select",
|
|
162
|
+
"multiselect"
|
|
163
|
+
]);
|
|
164
|
+
var SEARCHABLE_TYPES = /* @__PURE__ */ new Set([
|
|
165
|
+
"text",
|
|
166
|
+
"phone",
|
|
167
|
+
"status",
|
|
168
|
+
"select",
|
|
169
|
+
"multiselect"
|
|
170
|
+
]);
|
|
171
|
+
var SORTABLE_TYPES = /* @__PURE__ */ new Set([
|
|
172
|
+
"text",
|
|
173
|
+
"number",
|
|
174
|
+
"checkbox",
|
|
175
|
+
"date",
|
|
176
|
+
"phone",
|
|
177
|
+
"currency",
|
|
178
|
+
"status",
|
|
179
|
+
"select"
|
|
180
|
+
]);
|
|
181
|
+
function getAttributeCapabilities(attribute) {
|
|
182
|
+
const suppressed = Boolean(attribute.hidden || attribute.archived);
|
|
183
|
+
const active = !attribute.archived;
|
|
184
|
+
const computed = COMPUTED_TYPES.has(attribute.type);
|
|
185
|
+
const document2 = attribute.type === "document";
|
|
186
|
+
const edgeBacked = EDGE_BACKED_TYPES.has(attribute.type);
|
|
187
|
+
const cardinality = getAttributeCardinality(attribute);
|
|
188
|
+
const operators = suppressed ? [] : getRawFilterOperators(attribute);
|
|
189
|
+
const filterable = operators.length > 0 && !suppressed;
|
|
190
|
+
const searchable = !suppressed && SEARCHABLE_TYPES.has(attribute.type);
|
|
191
|
+
const sortable = !suppressed && SORTABLE_TYPES.has(attribute.type);
|
|
192
|
+
const groupable = !suppressed && isGroupableType(attribute.type);
|
|
193
|
+
const kanbanGroupable = !suppressed && attribute.type === "status";
|
|
194
|
+
const readOnly = computed || document2;
|
|
195
|
+
const editable = !(suppressed || readOnly);
|
|
196
|
+
const required = isRequiredForbidden(attribute.type) ? "forbidden" : "allowed";
|
|
197
|
+
const defaultValue = getDefaultValueCapability(attribute.type);
|
|
198
|
+
const maxItems = supportsMaxItems(attribute);
|
|
199
|
+
const placeholder = PLACEHOLDER_TYPES.has(attribute.type);
|
|
200
|
+
const warningWhenDeprecated = Boolean(attribute.deprecated && !suppressed);
|
|
201
|
+
return {
|
|
202
|
+
lifecycle: {
|
|
203
|
+
active,
|
|
204
|
+
deprecated: Boolean(attribute.deprecated),
|
|
205
|
+
visibleInArchitect: true,
|
|
206
|
+
visibleInRuntime: !suppressed,
|
|
207
|
+
warningWhenDeprecated
|
|
208
|
+
},
|
|
209
|
+
storage: {
|
|
210
|
+
mode: computed ? "computed" : edgeBacked ? "edge" : "value",
|
|
211
|
+
cardinality,
|
|
212
|
+
edgeBacked,
|
|
213
|
+
preservesExistingValues: Boolean(attribute.archived),
|
|
214
|
+
qualifiedProperties: supportsQualifiedProperties(attribute.type),
|
|
215
|
+
polymorphic: getPolymorphicCapability(attribute),
|
|
216
|
+
maxItems
|
|
217
|
+
},
|
|
218
|
+
authoring: {
|
|
219
|
+
editable,
|
|
220
|
+
readOnly,
|
|
221
|
+
directEdit: editable && DIRECT_EDIT_TYPES.has(attribute.type),
|
|
222
|
+
required,
|
|
223
|
+
defaultValue,
|
|
224
|
+
placeholder,
|
|
225
|
+
maxItems
|
|
226
|
+
},
|
|
227
|
+
presentation: {
|
|
228
|
+
formField: !suppressed && isFormFieldType(attribute.type),
|
|
229
|
+
tableCell: !suppressed && isTableCellType(attribute.type),
|
|
230
|
+
defaultColumn: !suppressed && isDefaultColumnType(attribute.type),
|
|
231
|
+
compactDisplay: !suppressed,
|
|
232
|
+
popoverEditor: !suppressed && isPopoverEditorType(attribute.type),
|
|
233
|
+
placeholder,
|
|
234
|
+
warningWhenDeprecated
|
|
235
|
+
},
|
|
236
|
+
query: {
|
|
237
|
+
filterable,
|
|
238
|
+
searchable,
|
|
239
|
+
sortable,
|
|
240
|
+
groupable,
|
|
241
|
+
kanbanGroupable,
|
|
242
|
+
operators
|
|
243
|
+
},
|
|
244
|
+
agent: {
|
|
245
|
+
readable: !suppressed,
|
|
246
|
+
writable: editable && !document2,
|
|
247
|
+
compactReadable: !suppressed,
|
|
248
|
+
contentReadable: !suppressed && isContentReadableType(attribute.type),
|
|
249
|
+
requiresDedicatedTool: document2
|
|
250
|
+
},
|
|
251
|
+
exchange: {
|
|
252
|
+
importable: !suppressed,
|
|
253
|
+
exportable: !suppressed,
|
|
254
|
+
schemaExportable: true,
|
|
255
|
+
metadataExportable: false
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function getAttributeFilterOperators(attribute) {
|
|
260
|
+
return getAttributeCapabilities(attribute).query.operators;
|
|
261
|
+
}
|
|
262
|
+
function isAttributeFilterable(attribute) {
|
|
263
|
+
return getAttributeCapabilities(attribute).query.filterable;
|
|
264
|
+
}
|
|
265
|
+
function isAttributeSearchable(attribute) {
|
|
266
|
+
return getAttributeCapabilities(attribute).query.searchable;
|
|
267
|
+
}
|
|
268
|
+
function isAttributeSortable(attribute) {
|
|
269
|
+
return getAttributeCapabilities(attribute).query.sortable;
|
|
270
|
+
}
|
|
271
|
+
function isAttributeGroupable(attribute) {
|
|
272
|
+
return getAttributeCapabilities(attribute).query.groupable;
|
|
273
|
+
}
|
|
274
|
+
function isAttributeKanbanGroupable(attribute) {
|
|
275
|
+
return getAttributeCapabilities(attribute).query.kanbanGroupable;
|
|
276
|
+
}
|
|
277
|
+
function getRollupFilterOperators(attr) {
|
|
278
|
+
const fn = attr.function;
|
|
279
|
+
if (fn === "earliest" || fn === "latest") {
|
|
280
|
+
return ATTRIBUTE_FILTER_OPERATORS.date;
|
|
281
|
+
}
|
|
282
|
+
if (fn === "original") {
|
|
283
|
+
const targetType = attr.targetAttributeType;
|
|
284
|
+
if (targetType && hasFilterOperatorEntry(targetType)) {
|
|
285
|
+
return ATTRIBUTE_FILTER_OPERATORS[targetType];
|
|
286
|
+
}
|
|
287
|
+
return ATTRIBUTE_FILTER_OPERATORS.number;
|
|
288
|
+
}
|
|
289
|
+
return ATTRIBUTE_FILTER_OPERATORS.number;
|
|
290
|
+
}
|
|
291
|
+
function getRawFilterOperators(attribute) {
|
|
292
|
+
if (attribute.type === "rollup") {
|
|
293
|
+
return getRollupFilterOperators(attribute);
|
|
294
|
+
}
|
|
295
|
+
return ATTRIBUTE_FILTER_OPERATORS[attribute.type] ?? [];
|
|
296
|
+
}
|
|
297
|
+
function getAttributeCardinality(attribute) {
|
|
298
|
+
if (attribute.type === "multiselect" || attribute.type === "document") return "many";
|
|
299
|
+
if (attribute.type === "file" && attribute.multiple === true) return "many";
|
|
300
|
+
if (attribute.type === "user" && attribute.multiple === true) return "many";
|
|
301
|
+
if (attribute.type === "relation") return attribute.cardinality === "many" ? "many" : "one";
|
|
302
|
+
return "one";
|
|
303
|
+
}
|
|
304
|
+
function getPolymorphicCapability(attribute) {
|
|
305
|
+
if (attribute.type !== "relation") return "none";
|
|
306
|
+
const relation2 = attribute;
|
|
307
|
+
const targets = relation2.targets ?? [];
|
|
308
|
+
if (targets.length === 1 && targets[0]?.object === "*") return "internal";
|
|
309
|
+
return targets.length > 1 ? "code-only" : "none";
|
|
310
|
+
}
|
|
311
|
+
function supportsMaxItems(attribute) {
|
|
312
|
+
if (attribute.type === "multiselect") return true;
|
|
313
|
+
if (attribute.type === "relation") return attribute.cardinality === "many";
|
|
314
|
+
if (attribute.type === "file") return attribute.multiple === true;
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
function supportsQualifiedProperties(type) {
|
|
318
|
+
return type === "relation" || type === "document";
|
|
319
|
+
}
|
|
320
|
+
function isRequiredForbidden(type) {
|
|
321
|
+
return type === "checkbox" || type === "formula" || type === "rollup";
|
|
322
|
+
}
|
|
323
|
+
function getDefaultValueCapability(type) {
|
|
324
|
+
if (type === "checkbox") return "automatic";
|
|
325
|
+
if (type === "formula" || type === "rollup") return "forbidden";
|
|
326
|
+
if (STATIC_DEFAULT_TYPES.has(type)) return "static";
|
|
327
|
+
return "forbidden";
|
|
328
|
+
}
|
|
329
|
+
function isDefaultColumnType(type) {
|
|
330
|
+
return isTableCellType(type);
|
|
331
|
+
}
|
|
332
|
+
function isTableCellType(type) {
|
|
333
|
+
return type !== "richtext" && type !== "file" && type !== "document";
|
|
334
|
+
}
|
|
335
|
+
function isFormFieldType(type) {
|
|
336
|
+
return type !== "richtext";
|
|
337
|
+
}
|
|
338
|
+
function isPopoverEditorType(type) {
|
|
339
|
+
return type === "select" || type === "status" || type === "multiselect" || type === "relation" || type === "user";
|
|
340
|
+
}
|
|
341
|
+
function isGroupableType(type) {
|
|
342
|
+
return type === "status" || type === "currency";
|
|
343
|
+
}
|
|
344
|
+
function isContentReadableType(type) {
|
|
345
|
+
return type === "richtext" || type === "document";
|
|
346
|
+
}
|
|
347
|
+
function hasFilterOperatorEntry(type) {
|
|
348
|
+
return Object.prototype.hasOwnProperty.call(ATTRIBUTE_FILTER_OPERATORS, type);
|
|
349
|
+
}
|
|
350
|
+
|
|
42
351
|
// src/types/attributes.ts
|
|
43
352
|
var RELATION_TARGET_ANY = "*";
|
|
44
353
|
function isUniversalRelation(attr) {
|
|
@@ -50,17 +359,8 @@ function isBilateralRelation(attr) {
|
|
|
50
359
|
function inferInverseCardinality(cardinality) {
|
|
51
360
|
return cardinality === "one" ? "many" : "many";
|
|
52
361
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"file",
|
|
56
|
-
"document",
|
|
57
|
-
"location",
|
|
58
|
-
"user",
|
|
59
|
-
"multiselect",
|
|
60
|
-
"relation"
|
|
61
|
-
]);
|
|
62
|
-
function isAttributeSortable(attr) {
|
|
63
|
-
return !NON_SORTABLE_TYPES.has(attr.type);
|
|
362
|
+
function isAttributeSortable2(attr) {
|
|
363
|
+
return isAttributeSortable(attr);
|
|
64
364
|
}
|
|
65
365
|
|
|
66
366
|
// src/types/document-layout.ts
|
|
@@ -82,95 +382,7 @@ function getErrorMessage(error) {
|
|
|
82
382
|
}
|
|
83
383
|
|
|
84
384
|
// src/types/filters.ts
|
|
85
|
-
var OPERATORS_BY_TYPE =
|
|
86
|
-
text: [
|
|
87
|
-
"is",
|
|
88
|
-
"is_not",
|
|
89
|
-
"contains",
|
|
90
|
-
"not_contains",
|
|
91
|
-
"starts_with",
|
|
92
|
-
"ends_with",
|
|
93
|
-
"is_empty",
|
|
94
|
-
"is_not_empty"
|
|
95
|
-
],
|
|
96
|
-
textarea: [
|
|
97
|
-
"is",
|
|
98
|
-
"is_not",
|
|
99
|
-
"contains",
|
|
100
|
-
"not_contains",
|
|
101
|
-
"starts_with",
|
|
102
|
-
"ends_with",
|
|
103
|
-
"is_empty",
|
|
104
|
-
"is_not_empty"
|
|
105
|
-
],
|
|
106
|
-
richtext: ["is_empty", "is_not_empty"],
|
|
107
|
-
number: ["eq", "neq", "lt", "gt", "lte", "gte", "is_empty", "is_not_empty"],
|
|
108
|
-
checkbox: ["is_checked", "is_not_checked"],
|
|
109
|
-
date: [
|
|
110
|
-
"is",
|
|
111
|
-
"is_not",
|
|
112
|
-
"before",
|
|
113
|
-
"after",
|
|
114
|
-
"on_or_before",
|
|
115
|
-
"on_or_after",
|
|
116
|
-
"is_within",
|
|
117
|
-
"day_month_eq",
|
|
118
|
-
"is_empty",
|
|
119
|
-
"is_not_empty"
|
|
120
|
-
],
|
|
121
|
-
phone: ["is", "is_not", "contains", "not_contains", "is_empty", "is_not_empty"],
|
|
122
|
-
currency: ["eq", "neq", "lt", "gt", "lte", "gte", "is_empty", "is_not_empty"],
|
|
123
|
-
status: ["is", "is_not", "any_of", "none_of", "is_empty", "is_not_empty"],
|
|
124
|
-
select: ["is", "is_not", "any_of", "none_of", "is_empty", "is_not_empty"],
|
|
125
|
-
multiselect: ["contains", "not_contains", "is_empty", "is_not_empty"],
|
|
126
|
-
location: ["contains", "is_empty", "is_not_empty"],
|
|
127
|
-
// Reference attribute types (user, relation) all route through the edge
|
|
128
|
-
// filter resolver, which normalizes is/is_not/any_of/none_of/contains/
|
|
129
|
-
// not_contains via `getOperatorPolarity` + `normalizeForEdgeRpc`. Keep the
|
|
130
|
-
// operator lists symmetric so the schema validation matches what the runtime
|
|
131
|
-
// actually supports.
|
|
132
|
-
// File and document attributes only expose presence operators — qualified
|
|
133
|
-
// filters (via `.qualifyWith()` on document) keep their own operator set
|
|
134
|
-
// based on the property type.
|
|
135
|
-
file: ["is_empty", "is_not_empty"],
|
|
136
|
-
user: [
|
|
137
|
-
"is",
|
|
138
|
-
"is_not",
|
|
139
|
-
"any_of",
|
|
140
|
-
"none_of",
|
|
141
|
-
"contains",
|
|
142
|
-
"not_contains",
|
|
143
|
-
"is_empty",
|
|
144
|
-
"is_not_empty"
|
|
145
|
-
],
|
|
146
|
-
relation: [
|
|
147
|
-
"is",
|
|
148
|
-
"is_not",
|
|
149
|
-
"any_of",
|
|
150
|
-
"none_of",
|
|
151
|
-
"contains",
|
|
152
|
-
"not_contains",
|
|
153
|
-
"is_empty",
|
|
154
|
-
"is_not_empty"
|
|
155
|
-
],
|
|
156
|
-
rating: ["eq", "neq", "lt", "gt", "lte", "gte", "is_empty", "is_not_empty"],
|
|
157
|
-
// Formula: operators depend on returnType, but we provide a general set
|
|
158
|
-
formula: [
|
|
159
|
-
"is",
|
|
160
|
-
"is_not",
|
|
161
|
-
"eq",
|
|
162
|
-
"neq",
|
|
163
|
-
"lt",
|
|
164
|
-
"gt",
|
|
165
|
-
"lte",
|
|
166
|
-
"gte",
|
|
167
|
-
"contains",
|
|
168
|
-
"not_contains",
|
|
169
|
-
"is_empty",
|
|
170
|
-
"is_not_empty"
|
|
171
|
-
],
|
|
172
|
-
document: ["is_empty", "is_not_empty"]
|
|
173
|
-
};
|
|
385
|
+
var OPERATORS_BY_TYPE = ATTRIBUTE_FILTER_OPERATORS;
|
|
174
386
|
var NO_VALUE_OPERATORS = [
|
|
175
387
|
"is_empty",
|
|
176
388
|
"is_not_empty",
|
|
@@ -180,19 +392,8 @@ var NO_VALUE_OPERATORS = [
|
|
|
180
392
|
function isNoValueOperator(operator) {
|
|
181
393
|
return NO_VALUE_OPERATORS.includes(operator);
|
|
182
394
|
}
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
if (fn === "earliest" || fn === "latest") {
|
|
186
|
-
return OPERATORS_BY_TYPE.date;
|
|
187
|
-
}
|
|
188
|
-
if (fn === "original") {
|
|
189
|
-
const targetType = attr.targetAttributeType;
|
|
190
|
-
if (targetType && Object.prototype.hasOwnProperty.call(OPERATORS_BY_TYPE, targetType)) {
|
|
191
|
-
return OPERATORS_BY_TYPE[targetType];
|
|
192
|
-
}
|
|
193
|
-
return OPERATORS_BY_TYPE.number;
|
|
194
|
-
}
|
|
195
|
-
return OPERATORS_BY_TYPE.number;
|
|
395
|
+
function getRollupFilterOperators2(attr) {
|
|
396
|
+
return getRollupFilterOperators(attr);
|
|
196
397
|
}
|
|
197
398
|
|
|
198
399
|
// src/types/forms/definition.ts
|
|
@@ -310,7 +511,6 @@ var SYSTEM_ATTRIBUTES = {
|
|
|
310
511
|
label: "Created time",
|
|
311
512
|
type: "date",
|
|
312
513
|
required: false,
|
|
313
|
-
disabled: true,
|
|
314
514
|
system: true,
|
|
315
515
|
icon: "clock-circle",
|
|
316
516
|
description: "When this record was created"
|
|
@@ -321,7 +521,6 @@ var SYSTEM_ATTRIBUTES = {
|
|
|
321
521
|
label: "Last edited time",
|
|
322
522
|
type: "date",
|
|
323
523
|
required: false,
|
|
324
|
-
disabled: true,
|
|
325
524
|
system: true,
|
|
326
525
|
icon: "clock-circle",
|
|
327
526
|
description: "When this record was last modified"
|
|
@@ -332,7 +531,6 @@ var SYSTEM_ATTRIBUTES = {
|
|
|
332
531
|
label: "Created by",
|
|
333
532
|
type: "user",
|
|
334
533
|
required: false,
|
|
335
|
-
disabled: true,
|
|
336
534
|
system: true,
|
|
337
535
|
multiple: false,
|
|
338
536
|
icon: "user",
|
|
@@ -344,7 +542,6 @@ var SYSTEM_ATTRIBUTES = {
|
|
|
344
542
|
label: "Last edited by",
|
|
345
543
|
type: "user",
|
|
346
544
|
required: false,
|
|
347
|
-
disabled: true,
|
|
348
545
|
system: true,
|
|
349
546
|
multiple: false,
|
|
350
547
|
icon: "user",
|
|
@@ -361,8 +558,6 @@ var SYSTEM_ATTRIBUTES = {
|
|
|
361
558
|
label: "Attachments",
|
|
362
559
|
type: "document",
|
|
363
560
|
required: false,
|
|
364
|
-
disabled: false,
|
|
365
|
-
// Editable via Documents tab
|
|
366
561
|
system: true,
|
|
367
562
|
hidden: true,
|
|
368
563
|
// Hidden from regular form views
|
|
@@ -458,7 +653,6 @@ function validateSlotAgainstConfig(slotName, mimeType, slots) {
|
|
|
458
653
|
// src/lib/qualify-with.ts
|
|
459
654
|
var ALLOWED_PROPERTY_TYPES = Object.freeze([
|
|
460
655
|
"text",
|
|
461
|
-
"textarea",
|
|
462
656
|
"number",
|
|
463
657
|
"checkbox",
|
|
464
658
|
"date",
|
|
@@ -467,7 +661,6 @@ var ALLOWED_PROPERTY_TYPES = Object.freeze([
|
|
|
467
661
|
"status",
|
|
468
662
|
"select",
|
|
469
663
|
"multiselect",
|
|
470
|
-
"rating",
|
|
471
664
|
"location"
|
|
472
665
|
]);
|
|
473
666
|
function buildPropertySchema(builders) {
|
|
@@ -507,6 +700,9 @@ function formatCheckbox(value) {
|
|
|
507
700
|
}
|
|
508
701
|
function formatNumber(value, attribute) {
|
|
509
702
|
if (typeof value !== "number") return String(value);
|
|
703
|
+
if (attribute.renderAs === "rating") {
|
|
704
|
+
return `${value}/${attribute.max ?? 5}`;
|
|
705
|
+
}
|
|
510
706
|
const decimals = attribute.decimals;
|
|
511
707
|
if (attribute.unit === "integer") {
|
|
512
708
|
return value.toLocaleString(void 0, {
|
|
@@ -608,18 +804,12 @@ function formatMultiselect(value, attribute) {
|
|
|
608
804
|
}
|
|
609
805
|
return value.join(", ");
|
|
610
806
|
}
|
|
611
|
-
function formatRating(value, attribute) {
|
|
612
|
-
if (typeof value !== "number") return String(value);
|
|
613
|
-
const max = attribute.max ?? 5;
|
|
614
|
-
return `${value}/${max}`;
|
|
615
|
-
}
|
|
616
807
|
function formatAttributeValue(value, attribute) {
|
|
617
808
|
if (value === null || value === void 0 || value === "") {
|
|
618
809
|
return EMPTY_VALUE_PLACEHOLDER;
|
|
619
810
|
}
|
|
620
811
|
switch (attribute.type) {
|
|
621
812
|
case "text":
|
|
622
|
-
case "textarea":
|
|
623
813
|
return formatText(value);
|
|
624
814
|
case "checkbox":
|
|
625
815
|
return formatCheckbox(value);
|
|
@@ -638,8 +828,6 @@ function formatAttributeValue(value, attribute) {
|
|
|
638
828
|
return formatSelect(value, attribute);
|
|
639
829
|
case "multiselect":
|
|
640
830
|
return formatMultiselect(value, attribute);
|
|
641
|
-
case "rating":
|
|
642
|
-
return formatRating(value, attribute);
|
|
643
831
|
// Unsupported types - return value as-is or placeholder
|
|
644
832
|
case "file":
|
|
645
833
|
case "user":
|
|
@@ -983,41 +1171,25 @@ Icon must be one of the predefined icons from @stndrds/constants.`
|
|
|
983
1171
|
);
|
|
984
1172
|
}
|
|
985
1173
|
}
|
|
986
|
-
function validateOptions(
|
|
987
|
-
if (options.length === 0) return;
|
|
988
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1174
|
+
function validateOptions(attributeName, options) {
|
|
989
1175
|
const values = /* @__PURE__ */ new Set();
|
|
990
1176
|
for (const option of options) {
|
|
991
|
-
if (!option.id?.trim()) {
|
|
992
|
-
throw new Error(
|
|
993
|
-
`[AttributeBuilder] Option in "${attributeName}" has an empty or missing id.`
|
|
994
|
-
);
|
|
995
|
-
}
|
|
996
1177
|
if (!option.value?.trim()) {
|
|
997
1178
|
throw new Error(
|
|
998
|
-
`[AttributeBuilder] Option
|
|
1179
|
+
`[AttributeBuilder] Option in "${attributeName}" has an empty or missing value.`
|
|
999
1180
|
);
|
|
1000
1181
|
}
|
|
1001
1182
|
if (!option.label?.trim()) {
|
|
1002
1183
|
throw new Error(
|
|
1003
|
-
`[AttributeBuilder] Option "${option.
|
|
1184
|
+
`[AttributeBuilder] Option "${option.value}" in "${attributeName}" has an empty or missing label.`
|
|
1004
1185
|
);
|
|
1005
1186
|
}
|
|
1006
|
-
if (ids.has(option.id)) {
|
|
1007
|
-
throw new Error(
|
|
1008
|
-
`[AttributeBuilder] Duplicate option id "${option.id}" in "${attributeName}".`
|
|
1009
|
-
);
|
|
1010
|
-
}
|
|
1011
|
-
ids.add(option.id);
|
|
1012
1187
|
if (values.has(option.value)) {
|
|
1013
1188
|
throw new Error(
|
|
1014
1189
|
`[AttributeBuilder] Duplicate option value "${option.value}" in "${attributeName}".`
|
|
1015
1190
|
);
|
|
1016
1191
|
}
|
|
1017
1192
|
values.add(option.value);
|
|
1018
|
-
if (option.icon) {
|
|
1019
|
-
validateIconName(option.icon, `option "${option.id}" of "${attributeName}"`);
|
|
1020
|
-
}
|
|
1021
1193
|
}
|
|
1022
1194
|
for (const option of options) {
|
|
1023
1195
|
if (option.inverse === void 0) continue;
|
|
@@ -1121,10 +1293,6 @@ var BaseAttributeBuilder = class {
|
|
|
1121
1293
|
this.attr.system = false;
|
|
1122
1294
|
return this;
|
|
1123
1295
|
}
|
|
1124
|
-
disabled() {
|
|
1125
|
-
this.attr.disabled = true;
|
|
1126
|
-
return this;
|
|
1127
|
-
}
|
|
1128
1296
|
hidden() {
|
|
1129
1297
|
this.attr.hidden = true;
|
|
1130
1298
|
return this;
|
|
@@ -1156,32 +1324,6 @@ var BaseAttributeBuilder = class {
|
|
|
1156
1324
|
this.attr.metadata = value;
|
|
1157
1325
|
return this;
|
|
1158
1326
|
}
|
|
1159
|
-
/**
|
|
1160
|
-
* Add a feature gate to conditionally show/hide/disable this attribute.
|
|
1161
|
-
*
|
|
1162
|
-
* @param flagName - Name of the feature flag to check
|
|
1163
|
-
* @param options - Optional configuration for expected value and fallback behavior
|
|
1164
|
-
*
|
|
1165
|
-
* @example Hide attribute when flag is disabled
|
|
1166
|
-
* ```typescript
|
|
1167
|
-
* text({ name: "aiSummary", label: "AI Summary" })
|
|
1168
|
-
* .featureGate("ai-features")
|
|
1169
|
-
* ```
|
|
1170
|
-
*
|
|
1171
|
-
* @example Disable attribute when tier is not enterprise
|
|
1172
|
-
* ```typescript
|
|
1173
|
-
* text({ name: "advancedField", label: "Advanced Field" })
|
|
1174
|
-
* .featureGate("tier", { expectedValue: "enterprise", fallback: "disable" })
|
|
1175
|
-
* ```
|
|
1176
|
-
*/
|
|
1177
|
-
featureGate(flagName, options) {
|
|
1178
|
-
this.attr.featureGate = {
|
|
1179
|
-
flag: flagName,
|
|
1180
|
-
expectedValue: options?.expectedValue,
|
|
1181
|
-
fallback: options?.fallback
|
|
1182
|
-
};
|
|
1183
|
-
return this;
|
|
1184
|
-
}
|
|
1185
1327
|
build() {
|
|
1186
1328
|
return this.attr;
|
|
1187
1329
|
}
|
|
@@ -1202,6 +1344,10 @@ var TextAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1202
1344
|
this.attr.pattern = value;
|
|
1203
1345
|
return this;
|
|
1204
1346
|
}
|
|
1347
|
+
multiline(value = true) {
|
|
1348
|
+
this.attr.multiline = value;
|
|
1349
|
+
return this;
|
|
1350
|
+
}
|
|
1205
1351
|
required() {
|
|
1206
1352
|
this.setRequired(true);
|
|
1207
1353
|
return this.asRequiredState();
|
|
@@ -1235,42 +1381,10 @@ var TextAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1235
1381
|
function text(config) {
|
|
1236
1382
|
return new TextAttributeBuilder(config.name, config.label);
|
|
1237
1383
|
}
|
|
1238
|
-
var TextAreaAttributeBuilder = class extends BaseAttributeBuilder {
|
|
1239
|
-
constructor(name, label) {
|
|
1240
|
-
super("textarea", name, label);
|
|
1241
|
-
}
|
|
1242
|
-
required() {
|
|
1243
|
-
this.setRequired(true);
|
|
1244
|
-
return this.asRequiredState();
|
|
1245
|
-
}
|
|
1246
|
-
optional() {
|
|
1247
|
-
this.setRequired(false);
|
|
1248
|
-
return this.asRequiredState();
|
|
1249
|
-
}
|
|
1250
|
-
};
|
|
1251
|
-
function textarea(config) {
|
|
1252
|
-
return new TextAreaAttributeBuilder(config.name, config.label);
|
|
1253
|
-
}
|
|
1254
1384
|
var RichtextAttributeBuilder = class extends BaseAttributeBuilder {
|
|
1255
1385
|
constructor(name, label) {
|
|
1256
1386
|
super("richtext", name, label);
|
|
1257
1387
|
}
|
|
1258
|
-
/**
|
|
1259
|
-
* Set enabled features for the richtext editor
|
|
1260
|
-
* If not called, all features are enabled by default
|
|
1261
|
-
*
|
|
1262
|
-
* @param list - Array of features to enable
|
|
1263
|
-
*
|
|
1264
|
-
* @example
|
|
1265
|
-
* ```typescript
|
|
1266
|
-
* richtext({ name: "content", label: "Content" })
|
|
1267
|
-
* .features(["headings", "bold", "italic", "lists", "links"])
|
|
1268
|
-
* ```
|
|
1269
|
-
*/
|
|
1270
|
-
features(list) {
|
|
1271
|
-
this.attr.features = list;
|
|
1272
|
-
return this;
|
|
1273
|
-
}
|
|
1274
1388
|
required() {
|
|
1275
1389
|
this.setRequired(true);
|
|
1276
1390
|
return this.asRequiredState();
|
|
@@ -1309,6 +1423,10 @@ var NumberAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1309
1423
|
this.attr.unit = "percentage";
|
|
1310
1424
|
return this;
|
|
1311
1425
|
}
|
|
1426
|
+
renderAs(value) {
|
|
1427
|
+
this.attr.renderAs = value;
|
|
1428
|
+
return this;
|
|
1429
|
+
}
|
|
1312
1430
|
required() {
|
|
1313
1431
|
this.setRequired(true);
|
|
1314
1432
|
return this.asRequiredState();
|
|
@@ -1422,7 +1540,7 @@ var StatusAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1422
1540
|
*/
|
|
1423
1541
|
options(value) {
|
|
1424
1542
|
const mutableOptions = asMutableArray(value);
|
|
1425
|
-
validateOptions(
|
|
1543
|
+
validateOptions(this.attr.name ?? "unknown", mutableOptions);
|
|
1426
1544
|
this.attr.options = mutableOptions;
|
|
1427
1545
|
return this.asPhantomState();
|
|
1428
1546
|
}
|
|
@@ -1431,7 +1549,7 @@ var StatusAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1431
1549
|
this.attr.options = [];
|
|
1432
1550
|
}
|
|
1433
1551
|
const allOptions = [...this.attr.options, config];
|
|
1434
|
-
validateOptions(
|
|
1552
|
+
validateOptions(this.attr.name ?? "unknown", allOptions);
|
|
1435
1553
|
this.attr.options.push(config);
|
|
1436
1554
|
return this;
|
|
1437
1555
|
}
|
|
@@ -1456,7 +1574,7 @@ var SelectAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1456
1574
|
*/
|
|
1457
1575
|
options(value) {
|
|
1458
1576
|
const mutableOptions = asMutableArray(value);
|
|
1459
|
-
validateOptions(
|
|
1577
|
+
validateOptions(this.attr.name ?? "unknown", mutableOptions);
|
|
1460
1578
|
this.attr.options = mutableOptions;
|
|
1461
1579
|
return this.asPhantomState();
|
|
1462
1580
|
}
|
|
@@ -1465,7 +1583,7 @@ var SelectAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1465
1583
|
this.attr.options = [];
|
|
1466
1584
|
}
|
|
1467
1585
|
const allOptions = [...this.attr.options, config];
|
|
1468
|
-
validateOptions(
|
|
1586
|
+
validateOptions(this.attr.name ?? "unknown", allOptions);
|
|
1469
1587
|
this.attr.options.push(config);
|
|
1470
1588
|
return this;
|
|
1471
1589
|
}
|
|
@@ -1490,7 +1608,7 @@ var MultiselectAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1490
1608
|
*/
|
|
1491
1609
|
options(value) {
|
|
1492
1610
|
const mutableOptions = asMutableArray(value);
|
|
1493
|
-
validateOptions(
|
|
1611
|
+
validateOptions(this.attr.name ?? "unknown", mutableOptions);
|
|
1494
1612
|
this.attr.options = mutableOptions;
|
|
1495
1613
|
return this.asPhantomState();
|
|
1496
1614
|
}
|
|
@@ -1499,7 +1617,7 @@ var MultiselectAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1499
1617
|
this.attr.options = [];
|
|
1500
1618
|
}
|
|
1501
1619
|
const allOptions = [...this.attr.options, config];
|
|
1502
|
-
validateOptions(
|
|
1620
|
+
validateOptions(this.attr.name ?? "unknown", allOptions);
|
|
1503
1621
|
this.attr.options.push(config);
|
|
1504
1622
|
return this;
|
|
1505
1623
|
}
|
|
@@ -1523,14 +1641,6 @@ var LocationAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1523
1641
|
this.attr.granularity = value;
|
|
1524
1642
|
return this;
|
|
1525
1643
|
}
|
|
1526
|
-
enableAutocomplete() {
|
|
1527
|
-
this.attr.enableAutocomplete = true;
|
|
1528
|
-
return this;
|
|
1529
|
-
}
|
|
1530
|
-
enableMap() {
|
|
1531
|
-
this.attr.enableMap = true;
|
|
1532
|
-
return this;
|
|
1533
|
-
}
|
|
1534
1644
|
defaultCountry(value) {
|
|
1535
1645
|
this.attr.defaultCountry = value;
|
|
1536
1646
|
return this;
|
|
@@ -1539,10 +1649,6 @@ var LocationAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1539
1649
|
this.attr.allowedCountries = value;
|
|
1540
1650
|
return this;
|
|
1541
1651
|
}
|
|
1542
|
-
displayFormat(value) {
|
|
1543
|
-
this.attr.displayFormat = value;
|
|
1544
|
-
return this;
|
|
1545
|
-
}
|
|
1546
1652
|
required() {
|
|
1547
1653
|
this.setRequired(true);
|
|
1548
1654
|
return this.asRequiredState();
|
|
@@ -1593,9 +1699,14 @@ function file(config) {
|
|
|
1593
1699
|
var UserAttributeBuilder = class extends BaseAttributeBuilder {
|
|
1594
1700
|
constructor(name, label) {
|
|
1595
1701
|
super("user", name, label);
|
|
1702
|
+
this.attr.types = ["user", "agent"];
|
|
1596
1703
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1704
|
+
types(types) {
|
|
1705
|
+
const unique = [...new Set(types)];
|
|
1706
|
+
if (unique.length === 0) {
|
|
1707
|
+
throw new Error("[AttributeBuilder] user.types must contain at least one type.");
|
|
1708
|
+
}
|
|
1709
|
+
this.attr.types = unique;
|
|
1599
1710
|
return this;
|
|
1600
1711
|
}
|
|
1601
1712
|
/**
|
|
@@ -1706,18 +1817,11 @@ var SingleRelationAttributeBuilder = class extends BaseRelationAttributeBuilder
|
|
|
1706
1817
|
);
|
|
1707
1818
|
if (this.attr.system === false) multiBuilder.runtime();
|
|
1708
1819
|
if (this.attr.hidden) multiBuilder.hidden();
|
|
1709
|
-
if (this.attr.disabled) multiBuilder.disabled();
|
|
1710
1820
|
if (this.attr.placeholder) multiBuilder.placeholder(this.attr.placeholder);
|
|
1711
1821
|
if (this.attr.description) multiBuilder.description(this.attr.description);
|
|
1712
1822
|
if (this.attr.icon) multiBuilder.icon(this.attr.icon);
|
|
1713
1823
|
if (this.attr.order !== void 0) multiBuilder.order(this.attr.order);
|
|
1714
1824
|
if (this.attr.metadata) multiBuilder.metadata(this.attr.metadata);
|
|
1715
|
-
if (this.attr.featureGate) {
|
|
1716
|
-
multiBuilder.featureGate(this.attr.featureGate.flag, {
|
|
1717
|
-
expectedValue: this.attr.featureGate.expectedValue,
|
|
1718
|
-
fallback: this.attr.featureGate.fallback
|
|
1719
|
-
});
|
|
1720
|
-
}
|
|
1721
1825
|
return multiBuilder;
|
|
1722
1826
|
}
|
|
1723
1827
|
required() {
|
|
@@ -1739,13 +1843,6 @@ var MultiRelationAttributeBuilder = class extends BaseRelationAttributeBuilder {
|
|
|
1739
1843
|
this.setRequired(true);
|
|
1740
1844
|
}
|
|
1741
1845
|
}
|
|
1742
|
-
/**
|
|
1743
|
-
* Set minimum number of relations required
|
|
1744
|
-
*/
|
|
1745
|
-
minItems(count) {
|
|
1746
|
-
this.attr.minItems = count;
|
|
1747
|
-
return this;
|
|
1748
|
-
}
|
|
1749
1846
|
/**
|
|
1750
1847
|
* Set maximum number of relations allowed
|
|
1751
1848
|
*/
|
|
@@ -1765,30 +1862,6 @@ var MultiRelationAttributeBuilder = class extends BaseRelationAttributeBuilder {
|
|
|
1765
1862
|
function relation(config) {
|
|
1766
1863
|
return new SingleRelationAttributeBuilder(config.name, config.label);
|
|
1767
1864
|
}
|
|
1768
|
-
var RatingAttributeBuilder = class extends BaseAttributeBuilder {
|
|
1769
|
-
constructor(name, label) {
|
|
1770
|
-
super("rating", name, label);
|
|
1771
|
-
}
|
|
1772
|
-
max(value) {
|
|
1773
|
-
this.attr.max = value;
|
|
1774
|
-
return this;
|
|
1775
|
-
}
|
|
1776
|
-
iconType(value) {
|
|
1777
|
-
this.attr.iconType = value;
|
|
1778
|
-
return this;
|
|
1779
|
-
}
|
|
1780
|
-
required() {
|
|
1781
|
-
this.setRequired(true);
|
|
1782
|
-
return this.asRequiredState();
|
|
1783
|
-
}
|
|
1784
|
-
optional() {
|
|
1785
|
-
this.setRequired(false);
|
|
1786
|
-
return this.asRequiredState();
|
|
1787
|
-
}
|
|
1788
|
-
};
|
|
1789
|
-
function rating(config) {
|
|
1790
|
-
return new RatingAttributeBuilder(config.name, config.label);
|
|
1791
|
-
}
|
|
1792
1865
|
var FormulaAttributeBuilder = class extends BaseAttributeBuilder {
|
|
1793
1866
|
constructor(name, label) {
|
|
1794
1867
|
super("formula", name, label);
|
|
@@ -1912,20 +1985,6 @@ var DocumentAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1912
1985
|
constructor(name, label) {
|
|
1913
1986
|
super("document", name, label);
|
|
1914
1987
|
}
|
|
1915
|
-
/**
|
|
1916
|
-
* Maximum number of documents.
|
|
1917
|
-
*/
|
|
1918
|
-
maxDocuments(count) {
|
|
1919
|
-
this.attr.maxDocuments = count;
|
|
1920
|
-
return this;
|
|
1921
|
-
}
|
|
1922
|
-
/**
|
|
1923
|
-
* Automatically trigger agent-compatible processing on upload.
|
|
1924
|
-
*/
|
|
1925
|
-
autoProcess() {
|
|
1926
|
-
this.attr.autoProcess = true;
|
|
1927
|
-
return this;
|
|
1928
|
-
}
|
|
1929
1988
|
/**
|
|
1930
1989
|
* Define named upload slots for this document attribute.
|
|
1931
1990
|
* An empty array falls back to a single default slot.
|
|
@@ -1934,38 +1993,6 @@ var DocumentAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1934
1993
|
this.attr.slots = configs.length === 0 ? [DEFAULT_DOCUMENT_SLOT] : configs;
|
|
1935
1994
|
return this;
|
|
1936
1995
|
}
|
|
1937
|
-
/**
|
|
1938
|
-
* Add a child attribute that qualifies the record→document edge.
|
|
1939
|
-
*
|
|
1940
|
-
* Equivalent to `.qualifyWith(builder)` for a single attribute, with the
|
|
1941
|
-
* difference that the accepted type is broader (`Attribute`) and the
|
|
1942
|
-
* accumulation is mutating rather than type-tracking. When the produced
|
|
1943
|
-
* attribute is one of the `ALLOWED_PROPERTY_TYPES`, it is also pushed
|
|
1944
|
-
* onto `attribute.properties.definitions`, so hydration and validation
|
|
1945
|
-
* treat the document as qualified — same effect as `.qualifyWith()`.
|
|
1946
|
-
*
|
|
1947
|
-
* Both `attribute.attributes` and `attribute.properties` are populated to
|
|
1948
|
-
* keep backwards compatibility with consumers that read either field.
|
|
1949
|
-
*
|
|
1950
|
-
* @example
|
|
1951
|
-
* document({ name: "invoices" })
|
|
1952
|
-
* .attribute(date({ name: "invoiceDate", label: "Date" }).required())
|
|
1953
|
-
* .attribute(select({ name: "status", label: "Statut" }).options([...]))
|
|
1954
|
-
*/
|
|
1955
|
-
attribute(builder) {
|
|
1956
|
-
const built = builder.build();
|
|
1957
|
-
if (!this.attr.attributes) this.attr.attributes = [];
|
|
1958
|
-
this.attr.attributes = [...this.attr.attributes, built];
|
|
1959
|
-
if (ALLOWED_PROPERTY_TYPES.includes(built.type)) {
|
|
1960
|
-
const props = this.attr.properties ?? {
|
|
1961
|
-
definitions: []
|
|
1962
|
-
};
|
|
1963
|
-
this.attr.properties = {
|
|
1964
|
-
definitions: [...props.definitions, built]
|
|
1965
|
-
};
|
|
1966
|
-
}
|
|
1967
|
-
return this;
|
|
1968
|
-
}
|
|
1969
1996
|
/**
|
|
1970
1997
|
* Add properties to qualify each document using existing attribute builders.
|
|
1971
1998
|
* Returns a new builder type that carries the inferred props shape for $infer.
|
|
@@ -2301,11 +2328,9 @@ function form(name, label) {
|
|
|
2301
2328
|
function resolveBuiltInTransform(targetType) {
|
|
2302
2329
|
const map = {
|
|
2303
2330
|
text: "toString",
|
|
2304
|
-
textarea: "toString",
|
|
2305
2331
|
richtext: "toString",
|
|
2306
2332
|
number: "toNumber",
|
|
2307
2333
|
currency: "toNumber",
|
|
2308
|
-
rating: "toNumber",
|
|
2309
2334
|
date: "toDate",
|
|
2310
2335
|
checkbox: "toBoolean"
|
|
2311
2336
|
};
|
|
@@ -2410,6 +2435,8 @@ var MigrationBuilder = class {
|
|
|
2410
2435
|
var ObjectBuilder = class {
|
|
2411
2436
|
constructor(config) {
|
|
2412
2437
|
this._migrations = [];
|
|
2438
|
+
this._sealed = false;
|
|
2439
|
+
this._toleratedAttributes = [];
|
|
2413
2440
|
this.validateName(config.name);
|
|
2414
2441
|
this.obj = {
|
|
2415
2442
|
name: config.name,
|
|
@@ -2470,6 +2497,43 @@ var ObjectBuilder = class {
|
|
|
2470
2497
|
this.obj.system = false;
|
|
2471
2498
|
return this;
|
|
2472
2499
|
}
|
|
2500
|
+
/**
|
|
2501
|
+
* Marks this object as sealed — no custom (user-created) attributes allowed.
|
|
2502
|
+
*
|
|
2503
|
+
* When sealed, `standards diff` reports an error if any custom attribute
|
|
2504
|
+
* exists in the DB that is not listed in `.tolerate()`.
|
|
2505
|
+
*
|
|
2506
|
+
* @example
|
|
2507
|
+
* ```typescript
|
|
2508
|
+
* object({ name: "invoice", label: "Invoice" })
|
|
2509
|
+
* .sealed()
|
|
2510
|
+
* .attribute(number({ name: "amount", label: "Amount" }))
|
|
2511
|
+
* ```
|
|
2512
|
+
*/
|
|
2513
|
+
sealed() {
|
|
2514
|
+
this._sealed = true;
|
|
2515
|
+
return this;
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Explicitly acknowledges custom attribute names on this object.
|
|
2519
|
+
*
|
|
2520
|
+
* On a sealed object: listed names will NOT trigger a CI failure in `standards diff`.
|
|
2521
|
+
* On an extensible object: purely documentary.
|
|
2522
|
+
*
|
|
2523
|
+
* @param names - Array of custom attribute names to tolerate
|
|
2524
|
+
*
|
|
2525
|
+
* @example
|
|
2526
|
+
* ```typescript
|
|
2527
|
+
* object({ name: "invoice", label: "Invoice" })
|
|
2528
|
+
* .sealed()
|
|
2529
|
+
* .tolerate(["legacy_ref", "old_status"])
|
|
2530
|
+
* .attribute(number({ name: "amount", label: "Amount" }))
|
|
2531
|
+
* ```
|
|
2532
|
+
*/
|
|
2533
|
+
tolerate(names) {
|
|
2534
|
+
this._toleratedAttributes = [...this._toleratedAttributes, ...names];
|
|
2535
|
+
return this;
|
|
2536
|
+
}
|
|
2473
2537
|
/**
|
|
2474
2538
|
* Add an attribute to the object with type accumulation
|
|
2475
2539
|
*
|
|
@@ -2622,7 +2686,9 @@ var ObjectBuilder = class {
|
|
|
2622
2686
|
pluralLabel: this._pluralLabel,
|
|
2623
2687
|
labelExpression: this._labelExpression,
|
|
2624
2688
|
schema_version,
|
|
2625
|
-
migrations: sortedVersions
|
|
2689
|
+
migrations: sortedVersions,
|
|
2690
|
+
sealed: this._sealed,
|
|
2691
|
+
toleratedAttributes: this._toleratedAttributes
|
|
2626
2692
|
};
|
|
2627
2693
|
}
|
|
2628
2694
|
/**
|
|
@@ -4511,9 +4577,9 @@ Available views: ${availableNames}`
|
|
|
4511
4577
|
var viewRegistry = new ViewRegistry();
|
|
4512
4578
|
|
|
4513
4579
|
// src/views/auto-generator.ts
|
|
4514
|
-
function
|
|
4580
|
+
function getRuntimeAttributes(attributes, excludeNames = []) {
|
|
4515
4581
|
return attributes.filter(
|
|
4516
|
-
(attr) => !(attr.
|
|
4582
|
+
(attr) => !(!getAttributeCapabilities(attr).lifecycle.visibleInRuntime || attr.system || excludeNames.includes(attr.name))
|
|
4517
4583
|
);
|
|
4518
4584
|
}
|
|
4519
4585
|
function sortAttributes(attributes) {
|
|
@@ -4524,9 +4590,9 @@ function sortAttributes(attributes) {
|
|
|
4524
4590
|
return a.name.localeCompare(b.name);
|
|
4525
4591
|
});
|
|
4526
4592
|
}
|
|
4527
|
-
function getDefaultFieldSpan(
|
|
4528
|
-
|
|
4529
|
-
|
|
4593
|
+
function getDefaultFieldSpan(attr) {
|
|
4594
|
+
if (attr.type === "text" && attr.multiline) return 12;
|
|
4595
|
+
switch (attr.type) {
|
|
4530
4596
|
case "richtext":
|
|
4531
4597
|
case "location":
|
|
4532
4598
|
return 12;
|
|
@@ -4539,7 +4605,7 @@ function getDefaultFieldSpan(type) {
|
|
|
4539
4605
|
function attributeToField(attr) {
|
|
4540
4606
|
return {
|
|
4541
4607
|
attribute: attr.name,
|
|
4542
|
-
span: getDefaultFieldSpan(attr
|
|
4608
|
+
span: getDefaultFieldSpan(attr)
|
|
4543
4609
|
};
|
|
4544
4610
|
}
|
|
4545
4611
|
function generateDefaultDetailView(object2, options = {}) {
|
|
@@ -4549,8 +4615,10 @@ function generateDefaultDetailView(object2, options = {}) {
|
|
|
4549
4615
|
excludeAttributes = [],
|
|
4550
4616
|
layout = "page"
|
|
4551
4617
|
} = options;
|
|
4552
|
-
const
|
|
4553
|
-
const sortedAttrs = sortAttributes(
|
|
4618
|
+
const runtimeAttrs = getRuntimeAttributes(object2.attributes, excludeAttributes);
|
|
4619
|
+
const sortedAttrs = sortAttributes(
|
|
4620
|
+
runtimeAttrs.filter((attr) => getAttributeCapabilities(attr).presentation.formField)
|
|
4621
|
+
);
|
|
4554
4622
|
const formTab = {
|
|
4555
4623
|
id: "form",
|
|
4556
4624
|
name: "form",
|
|
@@ -4605,8 +4673,10 @@ function generateDefaultDetailView(object2, options = {}) {
|
|
|
4605
4673
|
}
|
|
4606
4674
|
function generateDefaultListView(object2, options = {}) {
|
|
4607
4675
|
const { excludeAttributes = [], maxListColumns = 5 } = options;
|
|
4608
|
-
const
|
|
4609
|
-
const sortedAttrs = sortAttributes(
|
|
4676
|
+
const runtimeAttrs = getRuntimeAttributes(object2.attributes, excludeAttributes);
|
|
4677
|
+
const sortedAttrs = sortAttributes(
|
|
4678
|
+
runtimeAttrs.filter((attr) => getAttributeCapabilities(attr).presentation.defaultColumn)
|
|
4679
|
+
);
|
|
4610
4680
|
const columns = sortedAttrs.slice(0, maxListColumns).map((attr) => attr.name);
|
|
4611
4681
|
const config = {
|
|
4612
4682
|
tabs: [
|
|
@@ -4648,289 +4718,1709 @@ function mergeDetailTabs(generatedTabs, preservedTabs) {
|
|
|
4648
4718
|
if (preservedTabs.length === 0) {
|
|
4649
4719
|
return generatedTabs;
|
|
4650
4720
|
}
|
|
4651
|
-
const formTab = generatedTabs.find((t) => t.type === "form");
|
|
4652
|
-
const otherTabs = generatedTabs.filter((t) => t.type !== "form");
|
|
4653
|
-
const result = [];
|
|
4654
|
-
if (formTab) {
|
|
4655
|
-
result.push(formTab);
|
|
4721
|
+
const formTab = generatedTabs.find((t) => t.type === "form");
|
|
4722
|
+
const otherTabs = generatedTabs.filter((t) => t.type !== "form");
|
|
4723
|
+
const result = [];
|
|
4724
|
+
if (formTab) {
|
|
4725
|
+
result.push(formTab);
|
|
4726
|
+
}
|
|
4727
|
+
let order = 1;
|
|
4728
|
+
for (const tab of preservedTabs) {
|
|
4729
|
+
result.push({ ...tab, order: order++ });
|
|
4730
|
+
}
|
|
4731
|
+
for (const tab of otherTabs) {
|
|
4732
|
+
result.push({ ...tab, order: order++ });
|
|
4733
|
+
}
|
|
4734
|
+
return result;
|
|
4735
|
+
}
|
|
4736
|
+
function resetDetailViewToDefault(view, object2, options) {
|
|
4737
|
+
const { preserveMetadata = true, ...generateOptions } = options;
|
|
4738
|
+
const defaultView = generateDefaultDetailView(object2, generateOptions);
|
|
4739
|
+
const preservedTabs = getPreservedDetailTabs(view, options);
|
|
4740
|
+
const mergedTabs = mergeDetailTabs(defaultView.config.tabs, preservedTabs);
|
|
4741
|
+
const config = {
|
|
4742
|
+
...defaultView.config,
|
|
4743
|
+
tabs: mergedTabs
|
|
4744
|
+
};
|
|
4745
|
+
const resetView = {
|
|
4746
|
+
...defaultView,
|
|
4747
|
+
config
|
|
4748
|
+
};
|
|
4749
|
+
if (preserveMetadata) {
|
|
4750
|
+
resetView.id = view.id;
|
|
4751
|
+
resetView.name = view.name;
|
|
4752
|
+
resetView.label = view.label;
|
|
4753
|
+
resetView.description = view.description;
|
|
4754
|
+
resetView.icon = view.icon;
|
|
4755
|
+
}
|
|
4756
|
+
return resetView;
|
|
4757
|
+
}
|
|
4758
|
+
function resetListViewToDefault(view, object2, options) {
|
|
4759
|
+
const { preserveMetadata = true, ...generateOptions } = options;
|
|
4760
|
+
const defaultView = generateDefaultListView(object2, generateOptions);
|
|
4761
|
+
const resetView = {
|
|
4762
|
+
...defaultView
|
|
4763
|
+
};
|
|
4764
|
+
if (preserveMetadata) {
|
|
4765
|
+
resetView.id = view.id;
|
|
4766
|
+
resetView.name = view.name;
|
|
4767
|
+
resetView.label = view.label;
|
|
4768
|
+
resetView.description = view.description;
|
|
4769
|
+
resetView.icon = view.icon;
|
|
4770
|
+
}
|
|
4771
|
+
return resetView;
|
|
4772
|
+
}
|
|
4773
|
+
function resetViewToDefault(currentView, object2, options = {}) {
|
|
4774
|
+
if (isDetailView(currentView)) {
|
|
4775
|
+
return resetDetailViewToDefault(currentView, object2, options);
|
|
4776
|
+
}
|
|
4777
|
+
if (isListView(currentView)) {
|
|
4778
|
+
return resetListViewToDefault(currentView, object2, options);
|
|
4779
|
+
}
|
|
4780
|
+
throw new Error(`Reset not supported for view type: ${currentView.type}`);
|
|
4781
|
+
}
|
|
4782
|
+
|
|
4783
|
+
// src/views/index.ts
|
|
4784
|
+
function getActiveTab(config, tabId) {
|
|
4785
|
+
if (tabId) {
|
|
4786
|
+
const tab = config.tabs.find((t) => t.id === tabId);
|
|
4787
|
+
if (tab) return tab;
|
|
4788
|
+
}
|
|
4789
|
+
return config.tabs.find((t) => t.default) ?? config.tabs[0];
|
|
4790
|
+
}
|
|
4791
|
+
|
|
4792
|
+
// src/forms/registry.ts
|
|
4793
|
+
var FormRegistry = class {
|
|
4794
|
+
constructor() {
|
|
4795
|
+
this.forms = /* @__PURE__ */ new Map();
|
|
4796
|
+
}
|
|
4797
|
+
/**
|
|
4798
|
+
* Register a form definition (or array of definitions).
|
|
4799
|
+
* @throws Error if a form with the same name is already registered.
|
|
4800
|
+
*/
|
|
4801
|
+
register(formOrForms) {
|
|
4802
|
+
const forms = Array.isArray(formOrForms) ? formOrForms : [formOrForms];
|
|
4803
|
+
for (const f of forms) {
|
|
4804
|
+
if (this.forms.has(f.name)) {
|
|
4805
|
+
throw new Error(`[FormRegistry] Duplicate form: "${f.name}"`);
|
|
4806
|
+
}
|
|
4807
|
+
this.forms.set(f.name, f);
|
|
4808
|
+
}
|
|
4809
|
+
return this;
|
|
4810
|
+
}
|
|
4811
|
+
/**
|
|
4812
|
+
* Get a form definition by name.
|
|
4813
|
+
*/
|
|
4814
|
+
get(name) {
|
|
4815
|
+
return this.forms.get(name);
|
|
4816
|
+
}
|
|
4817
|
+
/**
|
|
4818
|
+
* Get all registered form definitions.
|
|
4819
|
+
*/
|
|
4820
|
+
getAll() {
|
|
4821
|
+
return Array.from(this.forms.values());
|
|
4822
|
+
}
|
|
4823
|
+
/**
|
|
4824
|
+
* List all registered form names.
|
|
4825
|
+
*/
|
|
4826
|
+
listNames() {
|
|
4827
|
+
return Array.from(this.forms.keys());
|
|
4828
|
+
}
|
|
4829
|
+
};
|
|
4830
|
+
var formRegistry = new FormRegistry();
|
|
4831
|
+
|
|
4832
|
+
// src/realtime/channels.ts
|
|
4833
|
+
function liveChannelKey(channel) {
|
|
4834
|
+
switch (channel.kind) {
|
|
4835
|
+
case "workspace":
|
|
4836
|
+
return `workspace:${encodeURIComponent(channel.workspaceId)}`;
|
|
4837
|
+
case "object":
|
|
4838
|
+
return `object:${encodeURIComponent(channel.objectId)}`;
|
|
4839
|
+
case "record":
|
|
4840
|
+
return `record:${encodeURIComponent(channel.objectId)}:${encodeURIComponent(channel.recordId)}`;
|
|
4841
|
+
case "view":
|
|
4842
|
+
return `view:${encodeURIComponent(channel.viewId)}`;
|
|
4843
|
+
case "agent":
|
|
4844
|
+
return `agent:${encodeURIComponent(channel.workspaceId)}`;
|
|
4845
|
+
case "user":
|
|
4846
|
+
return `user:${encodeURIComponent(channel.actorId)}`;
|
|
4847
|
+
case "conversation":
|
|
4848
|
+
return `conversation:${encodeURIComponent(channel.sessionId)}`;
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4851
|
+
function decodeLiveChannelPart(part) {
|
|
4852
|
+
try {
|
|
4853
|
+
return decodeURIComponent(part);
|
|
4854
|
+
} catch {
|
|
4855
|
+
return null;
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
function parseLiveChannelKey(key) {
|
|
4859
|
+
const parts = key.split(":");
|
|
4860
|
+
if (parts[0] === "workspace" && parts.length === 2 && parts[1]) {
|
|
4861
|
+
const workspaceId = decodeLiveChannelPart(parts[1]);
|
|
4862
|
+
return workspaceId ? { kind: "workspace", workspaceId } : null;
|
|
4863
|
+
}
|
|
4864
|
+
if (parts[0] === "object" && parts.length === 2 && parts[1]) {
|
|
4865
|
+
const objectId = decodeLiveChannelPart(parts[1]);
|
|
4866
|
+
return objectId ? { kind: "object", objectId } : null;
|
|
4867
|
+
}
|
|
4868
|
+
if (parts[0] === "record" && parts.length === 3 && parts[1] && parts[2]) {
|
|
4869
|
+
const objectId = decodeLiveChannelPart(parts[1]);
|
|
4870
|
+
const recordId = decodeLiveChannelPart(parts[2]);
|
|
4871
|
+
if (!(objectId && recordId)) return null;
|
|
4872
|
+
return {
|
|
4873
|
+
kind: "record",
|
|
4874
|
+
objectId,
|
|
4875
|
+
recordId
|
|
4876
|
+
};
|
|
4877
|
+
}
|
|
4878
|
+
if (parts[0] === "view" && parts.length === 2 && parts[1]) {
|
|
4879
|
+
const viewId = decodeLiveChannelPart(parts[1]);
|
|
4880
|
+
return viewId ? { kind: "view", viewId } : null;
|
|
4881
|
+
}
|
|
4882
|
+
if (parts[0] === "agent" && parts.length === 2 && parts[1]) {
|
|
4883
|
+
const workspaceId = decodeLiveChannelPart(parts[1]);
|
|
4884
|
+
return workspaceId ? { kind: "agent", workspaceId } : null;
|
|
4885
|
+
}
|
|
4886
|
+
if (parts[0] === "user" && parts.length === 2 && parts[1]) {
|
|
4887
|
+
const actorId = decodeLiveChannelPart(parts[1]);
|
|
4888
|
+
return actorId ? { kind: "user", actorId } : null;
|
|
4889
|
+
}
|
|
4890
|
+
if (parts[0] === "conversation" && parts.length === 2 && parts[1]) {
|
|
4891
|
+
const sessionId = decodeLiveChannelPart(parts[1]);
|
|
4892
|
+
return sessionId ? { kind: "conversation", sessionId } : null;
|
|
4893
|
+
}
|
|
4894
|
+
return null;
|
|
4895
|
+
}
|
|
4896
|
+
|
|
4897
|
+
// src/realtime/cursors.ts
|
|
4898
|
+
var LIVE_STREAM_START_CURSOR = "0-0";
|
|
4899
|
+
var LIVE_STREAM_CURSOR_PATTERN = /^(0|[1-9]\d*)-(0|[1-9]\d*)$/;
|
|
4900
|
+
function parseLiveStreamCursor(cursor) {
|
|
4901
|
+
if (!LIVE_STREAM_CURSOR_PATTERN.test(cursor)) {
|
|
4902
|
+
return null;
|
|
4903
|
+
}
|
|
4904
|
+
const [milliseconds = "0", sequence = "0"] = cursor.split("-");
|
|
4905
|
+
return [BigInt(milliseconds), BigInt(sequence)];
|
|
4906
|
+
}
|
|
4907
|
+
function isLiveStreamCursor(value) {
|
|
4908
|
+
return typeof value === "string" && parseLiveStreamCursor(value) !== null;
|
|
4909
|
+
}
|
|
4910
|
+
function assertLiveStreamCursor(cursor, label = "cursor") {
|
|
4911
|
+
if (cursor === null) {
|
|
4912
|
+
return;
|
|
4913
|
+
}
|
|
4914
|
+
if (!isLiveStreamCursor(cursor)) {
|
|
4915
|
+
throw new Error(`Invalid live stream cursor for ${label}: ${cursor}`);
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
function compareLiveStreamCursors(left, right) {
|
|
4919
|
+
const leftCursor = parseLiveStreamCursor(left);
|
|
4920
|
+
if (!leftCursor) {
|
|
4921
|
+
throw new Error(`Invalid live stream cursor for left: ${left}`);
|
|
4922
|
+
}
|
|
4923
|
+
const rightCursor = parseLiveStreamCursor(right);
|
|
4924
|
+
if (!rightCursor) {
|
|
4925
|
+
throw new Error(`Invalid live stream cursor for right: ${right}`);
|
|
4926
|
+
}
|
|
4927
|
+
const [leftMilliseconds, leftSequence] = leftCursor;
|
|
4928
|
+
const [rightMilliseconds, rightSequence] = rightCursor;
|
|
4929
|
+
if (leftMilliseconds !== rightMilliseconds) {
|
|
4930
|
+
return leftMilliseconds < rightMilliseconds ? -1 : 1;
|
|
4931
|
+
}
|
|
4932
|
+
if (leftSequence !== rightSequence) {
|
|
4933
|
+
return leftSequence < rightSequence ? -1 : 1;
|
|
4934
|
+
}
|
|
4935
|
+
return 0;
|
|
4936
|
+
}
|
|
4937
|
+
|
|
4938
|
+
// src/realtime/events.ts
|
|
4939
|
+
var LIVE_EVENT_TYPE_REGISTRY = {
|
|
4940
|
+
"record.patch": true,
|
|
4941
|
+
"record.deleted": true,
|
|
4942
|
+
"view.projection.stale": true,
|
|
4943
|
+
"agent.session.patch": true,
|
|
4944
|
+
"agent.unread.patch": true,
|
|
4945
|
+
"conversation.renamed": true,
|
|
4946
|
+
"channel.revoked": true
|
|
4947
|
+
};
|
|
4948
|
+
var LIVE_EVENT_TYPES = Object.keys(LIVE_EVENT_TYPE_REGISTRY);
|
|
4949
|
+
var COMPLETION_STATUSES = ["draft", "complete"];
|
|
4950
|
+
var VIEW_PROJECTION_STALE_REASONS = [
|
|
4951
|
+
"membership-unknown",
|
|
4952
|
+
"sort-unknown",
|
|
4953
|
+
"permissions-changed"
|
|
4954
|
+
];
|
|
4955
|
+
var CHANNEL_REVOKED_REASONS = [
|
|
4956
|
+
"permission-revoked",
|
|
4957
|
+
"workspace-left",
|
|
4958
|
+
"object-deleted"
|
|
4959
|
+
];
|
|
4960
|
+
function isLiveEventType(value) {
|
|
4961
|
+
return typeof value === "string" && LIVE_EVENT_TYPES.includes(value);
|
|
4962
|
+
}
|
|
4963
|
+
function isLiveEventPayload(payload) {
|
|
4964
|
+
if (!(isRecord(payload) && isLiveEventType(payload.type))) {
|
|
4965
|
+
return false;
|
|
4966
|
+
}
|
|
4967
|
+
return isLiveEventPayloadForType(payload.type, payload);
|
|
4968
|
+
}
|
|
4969
|
+
function isLiveEventPayloadForType(type, payload) {
|
|
4970
|
+
if (!isRecord(payload)) {
|
|
4971
|
+
return false;
|
|
4972
|
+
}
|
|
4973
|
+
switch (type) {
|
|
4974
|
+
case "record.patch":
|
|
4975
|
+
return isRecordPatchPayload(payload);
|
|
4976
|
+
case "record.deleted":
|
|
4977
|
+
return isRecordDeletedPayload(payload);
|
|
4978
|
+
case "view.projection.stale":
|
|
4979
|
+
return isViewProjectionStalePayload(payload);
|
|
4980
|
+
case "agent.session.patch":
|
|
4981
|
+
return isAgentSessionPatchPayload(payload);
|
|
4982
|
+
case "agent.unread.patch":
|
|
4983
|
+
return isAgentUnreadPatchPayload(payload);
|
|
4984
|
+
case "conversation.renamed":
|
|
4985
|
+
return isConversationRenamedPayload(payload);
|
|
4986
|
+
case "channel.revoked":
|
|
4987
|
+
return isChannelRevokedPayload(payload);
|
|
4988
|
+
default:
|
|
4989
|
+
return false;
|
|
4990
|
+
}
|
|
4991
|
+
}
|
|
4992
|
+
function isRecord(value) {
|
|
4993
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4994
|
+
}
|
|
4995
|
+
function isString(value) {
|
|
4996
|
+
return typeof value === "string";
|
|
4997
|
+
}
|
|
4998
|
+
function isVersion(value) {
|
|
4999
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
5000
|
+
}
|
|
5001
|
+
function hasString(value, key) {
|
|
5002
|
+
return isString(value[key]);
|
|
5003
|
+
}
|
|
5004
|
+
function hasVersion(value, key) {
|
|
5005
|
+
return isVersion(value[key]);
|
|
5006
|
+
}
|
|
5007
|
+
function hasOptional(value, key, predicate) {
|
|
5008
|
+
return !Object.hasOwn(value, key) || predicate(value[key]);
|
|
5009
|
+
}
|
|
5010
|
+
function isOneOf(value, values) {
|
|
5011
|
+
return typeof value === "string" && values.includes(value);
|
|
5012
|
+
}
|
|
5013
|
+
function isCompletionStatus(value) {
|
|
5014
|
+
return isOneOf(value, COMPLETION_STATUSES);
|
|
5015
|
+
}
|
|
5016
|
+
function isRecordFieldPatch(value) {
|
|
5017
|
+
return isRecord(value) && hasString(value, "fieldKey") && Object.hasOwn(value, "value") && hasVersion(value, "version");
|
|
5018
|
+
}
|
|
5019
|
+
function isRecordPatchPayload(payload) {
|
|
5020
|
+
return payload.type === "record.patch" && hasString(payload, "objectId") && hasString(payload, "recordId") && hasVersion(payload, "recordVersion") && hasOptional(payload, "label", isString) && hasOptional(payload, "completionStatus", isCompletionStatus) && hasOptional(payload, "metadata", isRecord) && Array.isArray(payload.fieldPatches) && payload.fieldPatches.every(isRecordFieldPatch) && hasString(payload, "updatedAt");
|
|
5021
|
+
}
|
|
5022
|
+
function isRecordDeletedPayload(payload) {
|
|
5023
|
+
return payload.type === "record.deleted" && hasString(payload, "objectId") && hasString(payload, "recordId") && hasVersion(payload, "recordVersion") && hasString(payload, "deletedAt");
|
|
5024
|
+
}
|
|
5025
|
+
function isViewProjectionStalePayload(payload) {
|
|
5026
|
+
return payload.type === "view.projection.stale" && hasString(payload, "viewId") && isOneOf(payload.reason, VIEW_PROJECTION_STALE_REASONS);
|
|
5027
|
+
}
|
|
5028
|
+
function isAgentSessionPatchPayload(payload) {
|
|
5029
|
+
return payload.type === "agent.session.patch" && hasString(payload, "workspaceId") && hasString(payload, "sessionId") && isRecord(payload.patch) && hasVersion(payload, "version") && hasString(payload, "updatedAt");
|
|
5030
|
+
}
|
|
5031
|
+
function isAgentUnreadPatchPayload(payload) {
|
|
5032
|
+
return payload.type === "agent.unread.patch" && hasString(payload, "workspaceId") && hasString(payload, "targetActorId") && hasString(payload, "sessionId") && hasVersion(payload, "unreadCount") && hasVersion(payload, "version") && hasString(payload, "updatedAt") && (!Object.hasOwn(payload, "session") || isRecord(payload.session) && payload.session.id === payload.sessionId);
|
|
5033
|
+
}
|
|
5034
|
+
function isConversationRenamedPayload(payload) {
|
|
5035
|
+
return payload.type === "conversation.renamed" && hasString(payload, "workspaceId") && hasString(payload, "sessionId") && hasString(payload, "title") && hasVersion(payload, "version") && hasString(payload, "updatedAt");
|
|
5036
|
+
}
|
|
5037
|
+
function isChannelRevokedPayload(payload) {
|
|
5038
|
+
return payload.type === "channel.revoked" && hasString(payload, "channel") && isOneOf(payload.reason, CHANNEL_REVOKED_REASONS);
|
|
5039
|
+
}
|
|
5040
|
+
|
|
5041
|
+
// src/realtime/protocol.ts
|
|
5042
|
+
function isRecord2(value) {
|
|
5043
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5044
|
+
}
|
|
5045
|
+
function payloadChannel(payload) {
|
|
5046
|
+
const { channel } = payload;
|
|
5047
|
+
return typeof channel === "string" && parseLiveChannelKey(channel) ? channel : null;
|
|
5048
|
+
}
|
|
5049
|
+
function getLiveProtocolPayloadChannel(payload) {
|
|
5050
|
+
return isRecord2(payload) ? payloadChannel(payload) : null;
|
|
5051
|
+
}
|
|
5052
|
+
function isLiveEventEnvelope(payload) {
|
|
5053
|
+
if (!isRecord2(payload)) return false;
|
|
5054
|
+
return typeof payload.id === "string" && isLiveStreamCursor(payload.id) && typeof payload.channel === "string" && typeof payload.type === "string" && typeof payload.occurredAt === "string" && isRecord2(payload.payload);
|
|
5055
|
+
}
|
|
5056
|
+
function parseLiveSubscribePayload(payload) {
|
|
5057
|
+
if (!isRecord2(payload)) return null;
|
|
5058
|
+
const channel = payloadChannel(payload);
|
|
5059
|
+
if (!channel) return null;
|
|
5060
|
+
const { cursor } = payload;
|
|
5061
|
+
if (cursor !== void 0 && cursor !== null) {
|
|
5062
|
+
if (typeof cursor !== "string" || !isLiveStreamCursor(cursor)) {
|
|
5063
|
+
return null;
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
return { channel, cursor: cursor ?? null };
|
|
5067
|
+
}
|
|
5068
|
+
function parseLiveUnsubscribePayload(payload) {
|
|
5069
|
+
if (payload === void 0 || payload === null) return {};
|
|
5070
|
+
if (!isRecord2(payload)) return null;
|
|
5071
|
+
if (!Object.hasOwn(payload, "channel")) {
|
|
5072
|
+
return {};
|
|
5073
|
+
}
|
|
5074
|
+
const channel = payloadChannel(payload);
|
|
5075
|
+
return channel ? { channel } : null;
|
|
5076
|
+
}
|
|
5077
|
+
function isLiveReplayCompletePayload(payload) {
|
|
5078
|
+
if (!isRecord2(payload)) return false;
|
|
5079
|
+
const channel = payloadChannel(payload);
|
|
5080
|
+
if (!channel) return false;
|
|
5081
|
+
return payload.cursor === null || typeof payload.cursor === "string" && isLiveStreamCursor(payload.cursor);
|
|
5082
|
+
}
|
|
5083
|
+
function isLiveGapPayload(payload) {
|
|
5084
|
+
if (!isRecord2(payload)) return false;
|
|
5085
|
+
return payloadChannel(payload) !== null;
|
|
5086
|
+
}
|
|
5087
|
+
function isLiveErrorPayload(payload) {
|
|
5088
|
+
if (!isRecord2(payload)) return false;
|
|
5089
|
+
return payloadChannel(payload) !== null;
|
|
5090
|
+
}
|
|
5091
|
+
|
|
5092
|
+
// src/computed/functions.ts
|
|
5093
|
+
var COMPUTED_FUNCTION_NAMES = [
|
|
5094
|
+
"if",
|
|
5095
|
+
"concat",
|
|
5096
|
+
"sum",
|
|
5097
|
+
"avg",
|
|
5098
|
+
"count",
|
|
5099
|
+
"min",
|
|
5100
|
+
"max",
|
|
5101
|
+
"earliest",
|
|
5102
|
+
"latest",
|
|
5103
|
+
"unique",
|
|
5104
|
+
"first",
|
|
5105
|
+
"percent_empty",
|
|
5106
|
+
"percent_not_empty",
|
|
5107
|
+
"count_empty",
|
|
5108
|
+
"count_not_empty",
|
|
5109
|
+
"count_unique"
|
|
5110
|
+
];
|
|
5111
|
+
var COMPUTED_FUNCTIONS = {
|
|
5112
|
+
if: { name: "if", returnType: "branch", minArgs: 3, maxArgs: 3 },
|
|
5113
|
+
concat: { name: "concat", returnType: { kind: "scalar", type: "text" }, minArgs: 1 },
|
|
5114
|
+
sum: { name: "sum", returnType: { kind: "scalar", type: "number" }, minArgs: 1, maxArgs: 1 },
|
|
5115
|
+
avg: { name: "avg", returnType: { kind: "scalar", type: "number" }, minArgs: 1, maxArgs: 1 },
|
|
5116
|
+
count: { name: "count", returnType: { kind: "scalar", type: "number" }, minArgs: 1, maxArgs: 1 },
|
|
5117
|
+
min: { name: "min", returnType: "source_item", minArgs: 1, maxArgs: 1 },
|
|
5118
|
+
max: { name: "max", returnType: "source_item", minArgs: 1, maxArgs: 1 },
|
|
5119
|
+
earliest: {
|
|
5120
|
+
name: "earliest",
|
|
5121
|
+
returnType: { kind: "scalar", type: "date" },
|
|
5122
|
+
minArgs: 1,
|
|
5123
|
+
maxArgs: 1
|
|
5124
|
+
},
|
|
5125
|
+
latest: { name: "latest", returnType: { kind: "scalar", type: "date" }, minArgs: 1, maxArgs: 1 },
|
|
5126
|
+
unique: { name: "unique", returnType: "argument", minArgs: 1, maxArgs: 1 },
|
|
5127
|
+
first: { name: "first", returnType: "source_item", minArgs: 1, maxArgs: 1 },
|
|
5128
|
+
percent_empty: {
|
|
5129
|
+
name: "percent_empty",
|
|
5130
|
+
returnType: { kind: "scalar", type: "number" },
|
|
5131
|
+
minArgs: 1,
|
|
5132
|
+
maxArgs: 1
|
|
5133
|
+
},
|
|
5134
|
+
percent_not_empty: {
|
|
5135
|
+
name: "percent_not_empty",
|
|
5136
|
+
returnType: { kind: "scalar", type: "number" },
|
|
5137
|
+
minArgs: 1,
|
|
5138
|
+
maxArgs: 1
|
|
5139
|
+
},
|
|
5140
|
+
count_empty: {
|
|
5141
|
+
name: "count_empty",
|
|
5142
|
+
returnType: { kind: "scalar", type: "number" },
|
|
5143
|
+
minArgs: 1,
|
|
5144
|
+
maxArgs: 1
|
|
5145
|
+
},
|
|
5146
|
+
count_not_empty: {
|
|
5147
|
+
name: "count_not_empty",
|
|
5148
|
+
returnType: { kind: "scalar", type: "number" },
|
|
5149
|
+
minArgs: 1,
|
|
5150
|
+
maxArgs: 1
|
|
5151
|
+
},
|
|
5152
|
+
count_unique: {
|
|
5153
|
+
name: "count_unique",
|
|
5154
|
+
returnType: { kind: "scalar", type: "number" },
|
|
5155
|
+
minArgs: 1,
|
|
5156
|
+
maxArgs: 1
|
|
5157
|
+
}
|
|
5158
|
+
};
|
|
5159
|
+
function isComputedFunctionName(name) {
|
|
5160
|
+
return Object.hasOwn(COMPUTED_FUNCTIONS, name);
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5163
|
+
// src/computed/parser.ts
|
|
5164
|
+
var ComputedFormulaParseError = class extends Error {
|
|
5165
|
+
constructor(message) {
|
|
5166
|
+
super(message);
|
|
5167
|
+
this.name = "ComputedFormulaParseError";
|
|
5168
|
+
}
|
|
5169
|
+
};
|
|
5170
|
+
function parseComputedFormula(expression) {
|
|
5171
|
+
const parser = new Parser(tokenize(expression));
|
|
5172
|
+
const ast = parser.parseExpression();
|
|
5173
|
+
parser.expectEnd();
|
|
5174
|
+
return ast;
|
|
5175
|
+
}
|
|
5176
|
+
function tokenize(expression) {
|
|
5177
|
+
const tokens = [];
|
|
5178
|
+
let index = 0;
|
|
5179
|
+
while (index < expression.length) {
|
|
5180
|
+
const char = expression[index];
|
|
5181
|
+
if (char === " " || char === " " || char === "\n" || char === "\r") {
|
|
5182
|
+
index += 1;
|
|
5183
|
+
continue;
|
|
5184
|
+
}
|
|
5185
|
+
if (char === '"' || char === "'") {
|
|
5186
|
+
const result = scanString(expression, index, char);
|
|
5187
|
+
tokens.push({ kind: "string", value: result.value, position: index });
|
|
5188
|
+
index = result.nextIndex;
|
|
5189
|
+
continue;
|
|
5190
|
+
}
|
|
5191
|
+
if (isDigit(char)) {
|
|
5192
|
+
const result = scanNumber(expression, index);
|
|
5193
|
+
tokens.push({ kind: "number", value: result.value, position: index });
|
|
5194
|
+
index = result.nextIndex;
|
|
5195
|
+
continue;
|
|
5196
|
+
}
|
|
5197
|
+
if (isIdentifierStart(char)) {
|
|
5198
|
+
const result = scanIdentifier(expression, index);
|
|
5199
|
+
const canonical = result.value.toLowerCase();
|
|
5200
|
+
if (canonical === "true") {
|
|
5201
|
+
tokens.push({ kind: "boolean", value: true, position: index });
|
|
5202
|
+
} else if (canonical === "false") {
|
|
5203
|
+
tokens.push({ kind: "boolean", value: false, position: index });
|
|
5204
|
+
} else if (canonical === "null") {
|
|
5205
|
+
tokens.push({ kind: "null", position: index });
|
|
5206
|
+
} else {
|
|
5207
|
+
tokens.push({ kind: "identifier", value: result.value, position: index });
|
|
5208
|
+
}
|
|
5209
|
+
index = result.nextIndex;
|
|
5210
|
+
continue;
|
|
5211
|
+
}
|
|
5212
|
+
const twoChar = expression.slice(index, index + 2);
|
|
5213
|
+
if (twoChar === ">=" || twoChar === "<=" || twoChar === "==" || twoChar === "!=") {
|
|
5214
|
+
tokens.push({ kind: "operator", value: twoChar, position: index });
|
|
5215
|
+
index += 2;
|
|
5216
|
+
continue;
|
|
5217
|
+
}
|
|
5218
|
+
if (char === ">" || char === "<" || char === "+" || char === "-" || char === "*" || char === "/") {
|
|
5219
|
+
tokens.push({ kind: "operator", value: char, position: index });
|
|
5220
|
+
index += 1;
|
|
5221
|
+
continue;
|
|
5222
|
+
}
|
|
5223
|
+
if (char === "(" || char === ")" || char === "," || char === ".") {
|
|
5224
|
+
tokens.push({ kind: "punctuation", value: char, position: index });
|
|
5225
|
+
index += 1;
|
|
5226
|
+
continue;
|
|
5227
|
+
}
|
|
5228
|
+
throw new ComputedFormulaParseError(`Unexpected character "${char}" at position ${index}`);
|
|
5229
|
+
}
|
|
5230
|
+
return tokens;
|
|
5231
|
+
}
|
|
5232
|
+
function scanString(expression, startIndex, delimiter = '"') {
|
|
5233
|
+
let value = "";
|
|
5234
|
+
let index = startIndex + 1;
|
|
5235
|
+
while (index < expression.length) {
|
|
5236
|
+
const char = expression[index];
|
|
5237
|
+
if (char === delimiter) {
|
|
5238
|
+
return { value, nextIndex: index + 1 };
|
|
5239
|
+
}
|
|
5240
|
+
if (char === "\\") {
|
|
5241
|
+
const escaped = expression[index + 1];
|
|
5242
|
+
if (escaped === void 0) {
|
|
5243
|
+
throw new ComputedFormulaParseError(`Unterminated string literal at position ${index}`);
|
|
5244
|
+
}
|
|
5245
|
+
value += decodeEscape(escaped, index);
|
|
5246
|
+
index += 2;
|
|
5247
|
+
continue;
|
|
5248
|
+
}
|
|
5249
|
+
value += char;
|
|
5250
|
+
index += 1;
|
|
5251
|
+
}
|
|
5252
|
+
throw new ComputedFormulaParseError(`Unterminated string literal at position ${startIndex}`);
|
|
5253
|
+
}
|
|
5254
|
+
function decodeEscape(char, position) {
|
|
5255
|
+
switch (char) {
|
|
5256
|
+
case '"':
|
|
5257
|
+
case "\\":
|
|
5258
|
+
return char;
|
|
5259
|
+
case "n":
|
|
5260
|
+
return "\n";
|
|
5261
|
+
case "r":
|
|
5262
|
+
return "\r";
|
|
5263
|
+
case "t":
|
|
5264
|
+
return " ";
|
|
5265
|
+
default:
|
|
5266
|
+
throw new ComputedFormulaParseError(
|
|
5267
|
+
`Unknown escape sequence "\\${char}" at position ${position}`
|
|
5268
|
+
);
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
5271
|
+
function scanNumber(expression, startIndex) {
|
|
5272
|
+
let index = startIndex;
|
|
5273
|
+
while (index < expression.length && isDigit(expression[index])) {
|
|
5274
|
+
index += 1;
|
|
5275
|
+
}
|
|
5276
|
+
if (expression[index] === ".") {
|
|
5277
|
+
const decimalStart = index;
|
|
5278
|
+
index += 1;
|
|
5279
|
+
if (!isDigit(expression[index])) {
|
|
5280
|
+
throw new ComputedFormulaParseError(`Invalid number literal at position ${decimalStart}`);
|
|
5281
|
+
}
|
|
5282
|
+
while (index < expression.length && isDigit(expression[index])) {
|
|
5283
|
+
index += 1;
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
5286
|
+
const raw = expression.slice(startIndex, index);
|
|
5287
|
+
const value = Number(raw);
|
|
5288
|
+
if (!Number.isFinite(value)) {
|
|
5289
|
+
throw new ComputedFormulaParseError(`Invalid number literal "${raw}"`);
|
|
5290
|
+
}
|
|
5291
|
+
return { value, nextIndex: index };
|
|
5292
|
+
}
|
|
5293
|
+
function scanIdentifier(expression, startIndex) {
|
|
5294
|
+
let index = startIndex + 1;
|
|
5295
|
+
while (index < expression.length && isIdentifierPart(expression[index])) {
|
|
5296
|
+
index += 1;
|
|
5297
|
+
}
|
|
5298
|
+
return { value: expression.slice(startIndex, index), nextIndex: index };
|
|
5299
|
+
}
|
|
5300
|
+
function isDigit(char) {
|
|
5301
|
+
return char !== void 0 && char >= "0" && char <= "9";
|
|
5302
|
+
}
|
|
5303
|
+
function isIdentifierStart(char) {
|
|
5304
|
+
return char !== void 0 && /[A-Za-z_]/.test(char);
|
|
5305
|
+
}
|
|
5306
|
+
function isIdentifierPart(char) {
|
|
5307
|
+
return char !== void 0 && /[A-Za-z0-9_]/.test(char);
|
|
5308
|
+
}
|
|
5309
|
+
var Parser = class {
|
|
5310
|
+
constructor(tokens) {
|
|
5311
|
+
this.tokens = tokens;
|
|
5312
|
+
this.position = 0;
|
|
5313
|
+
}
|
|
5314
|
+
parseExpression() {
|
|
5315
|
+
return this.parseBinaryExpression(0);
|
|
5316
|
+
}
|
|
5317
|
+
expectEnd() {
|
|
5318
|
+
const token = this.peek();
|
|
5319
|
+
if (token !== void 0) {
|
|
5320
|
+
throw new ComputedFormulaParseError(
|
|
5321
|
+
`Unexpected token ${describeToken(token)} at position ${token.position}`
|
|
5322
|
+
);
|
|
5323
|
+
}
|
|
5324
|
+
}
|
|
5325
|
+
parseBinaryExpression(minPrecedence) {
|
|
5326
|
+
let left = this.parsePrimary();
|
|
5327
|
+
while (true) {
|
|
5328
|
+
const token = this.peek();
|
|
5329
|
+
if (token?.kind !== "operator") {
|
|
5330
|
+
return left;
|
|
5331
|
+
}
|
|
5332
|
+
const precedence = getOperatorPrecedence(token.value);
|
|
5333
|
+
if (precedence < minPrecedence) {
|
|
5334
|
+
return left;
|
|
5335
|
+
}
|
|
5336
|
+
const operator = token.value;
|
|
5337
|
+
this.consume();
|
|
5338
|
+
const right = this.parseBinaryExpression(precedence + 1);
|
|
5339
|
+
left = { kind: "binary", operator, left, right };
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
5342
|
+
parsePrimary() {
|
|
5343
|
+
const token = this.consume();
|
|
5344
|
+
if (token === void 0) {
|
|
5345
|
+
throw new ComputedFormulaParseError("Unexpected end of expression");
|
|
5346
|
+
}
|
|
5347
|
+
switch (token.kind) {
|
|
5348
|
+
case "string":
|
|
5349
|
+
case "number":
|
|
5350
|
+
case "boolean":
|
|
5351
|
+
return { kind: "literal", value: token.value };
|
|
5352
|
+
case "null":
|
|
5353
|
+
return { kind: "literal", value: null };
|
|
5354
|
+
case "identifier":
|
|
5355
|
+
return this.parseIdentifier(token.value);
|
|
5356
|
+
case "punctuation":
|
|
5357
|
+
if (token.value === "(") {
|
|
5358
|
+
const expression = this.parseExpression();
|
|
5359
|
+
this.expectPunctuation(")");
|
|
5360
|
+
return expression;
|
|
5361
|
+
}
|
|
5362
|
+
break;
|
|
5363
|
+
case "operator":
|
|
5364
|
+
if (token.value === "-") {
|
|
5365
|
+
const next = this.consume();
|
|
5366
|
+
if (next?.kind === "number") {
|
|
5367
|
+
return { kind: "literal", value: -next.value };
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
break;
|
|
5371
|
+
}
|
|
5372
|
+
throw new ComputedFormulaParseError(
|
|
5373
|
+
`Expected literal, path, call, or parenthesized expression at position ${token.position}`
|
|
5374
|
+
);
|
|
5375
|
+
}
|
|
5376
|
+
parseIdentifier(identifier) {
|
|
5377
|
+
if (this.matchPunctuation("(")) {
|
|
5378
|
+
const args = [];
|
|
5379
|
+
if (!this.matchPunctuation(")")) {
|
|
5380
|
+
do {
|
|
5381
|
+
args.push(this.parseExpression());
|
|
5382
|
+
} while (this.matchPunctuation(","));
|
|
5383
|
+
this.expectPunctuation(")");
|
|
5384
|
+
}
|
|
5385
|
+
return { kind: "call", functionName: identifier.toLowerCase(), args };
|
|
5386
|
+
}
|
|
5387
|
+
const parts = [identifier];
|
|
5388
|
+
while (this.matchPunctuation(".")) {
|
|
5389
|
+
const next = this.consume();
|
|
5390
|
+
if (next?.kind !== "identifier") {
|
|
5391
|
+
const position = next?.position ?? this.previousPosition();
|
|
5392
|
+
throw new ComputedFormulaParseError(
|
|
5393
|
+
`Expected attribute name after path separator at position ${position}`
|
|
5394
|
+
);
|
|
5395
|
+
}
|
|
5396
|
+
parts.push(next.value);
|
|
5397
|
+
}
|
|
5398
|
+
return { kind: "path", parts };
|
|
5399
|
+
}
|
|
5400
|
+
expectPunctuation(value) {
|
|
5401
|
+
if (!this.matchPunctuation(value)) {
|
|
5402
|
+
const token = this.peek();
|
|
5403
|
+
const position = token?.position ?? this.previousPosition();
|
|
5404
|
+
throw new ComputedFormulaParseError(`Expected "${value}" at position ${position}`);
|
|
5405
|
+
}
|
|
5406
|
+
}
|
|
5407
|
+
matchPunctuation(value) {
|
|
5408
|
+
const token = this.peek();
|
|
5409
|
+
if (token?.kind === "punctuation" && token.value === value) {
|
|
5410
|
+
this.consume();
|
|
5411
|
+
return true;
|
|
5412
|
+
}
|
|
5413
|
+
return false;
|
|
5414
|
+
}
|
|
5415
|
+
peek() {
|
|
5416
|
+
return this.tokens[this.position];
|
|
5417
|
+
}
|
|
5418
|
+
consume() {
|
|
5419
|
+
const token = this.tokens[this.position];
|
|
5420
|
+
this.position += 1;
|
|
5421
|
+
return token;
|
|
5422
|
+
}
|
|
5423
|
+
previousPosition() {
|
|
5424
|
+
const previous = this.tokens[this.position - 1];
|
|
5425
|
+
return previous?.position ?? 0;
|
|
5426
|
+
}
|
|
5427
|
+
};
|
|
5428
|
+
function describeToken(token) {
|
|
5429
|
+
switch (token.kind) {
|
|
5430
|
+
case "identifier":
|
|
5431
|
+
case "number":
|
|
5432
|
+
case "string":
|
|
5433
|
+
case "boolean":
|
|
5434
|
+
case "operator":
|
|
5435
|
+
case "punctuation":
|
|
5436
|
+
return `"${String(token.value)}"`;
|
|
5437
|
+
case "null":
|
|
5438
|
+
return '"null"';
|
|
5439
|
+
default: {
|
|
5440
|
+
const exhaustive = token;
|
|
5441
|
+
return exhaustive;
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
function getOperatorPrecedence(operator) {
|
|
5446
|
+
switch (operator) {
|
|
5447
|
+
case "*":
|
|
5448
|
+
case "/":
|
|
5449
|
+
return 3;
|
|
5450
|
+
case "+":
|
|
5451
|
+
case "-":
|
|
5452
|
+
return 2;
|
|
5453
|
+
case ">":
|
|
5454
|
+
case "<":
|
|
5455
|
+
case ">=":
|
|
5456
|
+
case "<=":
|
|
5457
|
+
case "==":
|
|
5458
|
+
case "!=":
|
|
5459
|
+
return 1;
|
|
5460
|
+
default: {
|
|
5461
|
+
const exhaustive = operator;
|
|
5462
|
+
return exhaustive;
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
|
|
5467
|
+
// src/computed/compiler.ts
|
|
5468
|
+
var ComputedFormulaCompileError = class extends Error {
|
|
5469
|
+
constructor(message) {
|
|
5470
|
+
super(message);
|
|
5471
|
+
this.name = "ComputedFormulaCompileError";
|
|
5472
|
+
}
|
|
5473
|
+
};
|
|
5474
|
+
function compileComputedFormula(input) {
|
|
5475
|
+
const ast = parseCompileFormula(input.expression);
|
|
5476
|
+
const inferred = inferNode(ast, input.schema);
|
|
5477
|
+
const inferredReturnType = valueTypeToReturnType(inferred.valueType);
|
|
5478
|
+
if (inferredReturnType !== input.returnType) {
|
|
5479
|
+
throw new ComputedFormulaCompileError(
|
|
5480
|
+
`Computed formula "${input.attributeName}" returns "${inferredReturnType}", expected "${input.returnType}"`
|
|
5481
|
+
);
|
|
5482
|
+
}
|
|
5483
|
+
if (isSelectLikeReturnType(input.returnType) && inferred.optionsSource === void 0) {
|
|
5484
|
+
throw new ComputedFormulaCompileError(
|
|
5485
|
+
`Computed formula "${input.attributeName}" returns "${input.returnType}" without optionsSource`
|
|
5486
|
+
);
|
|
5487
|
+
}
|
|
5488
|
+
const optionsDependencies = inferred.optionsSource === void 0 ? [] : [{ kind: "options", source: inferred.optionsSource }];
|
|
5489
|
+
const dependencies = uniqueDependencies([...inferred.dependencies, ...optionsDependencies]);
|
|
5490
|
+
return {
|
|
5491
|
+
kind: "formula",
|
|
5492
|
+
attributeName: input.attributeName,
|
|
5493
|
+
expression: input.expression,
|
|
5494
|
+
returnType: input.returnType,
|
|
5495
|
+
valueType: inferred.valueType,
|
|
5496
|
+
dependencies,
|
|
5497
|
+
...inferred.optionsSource ? { optionsSource: inferred.optionsSource } : {},
|
|
5498
|
+
...inferred.targetAttributeType ? { targetAttributeType: inferred.targetAttributeType } : {},
|
|
5499
|
+
dependenciesHash: createDependenciesHash({
|
|
5500
|
+
expression: input.expression,
|
|
5501
|
+
returnType: input.returnType,
|
|
5502
|
+
dependencies
|
|
5503
|
+
})
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
function assertAcyclicComputedDependencies(nodes) {
|
|
5507
|
+
const graph = new Map(nodes.map((node) => [node.attributeName, node]));
|
|
5508
|
+
const visited = /* @__PURE__ */ new Set();
|
|
5509
|
+
const visit = (attributeName, activePath) => {
|
|
5510
|
+
const cycleStart = activePath.indexOf(attributeName);
|
|
5511
|
+
if (cycleStart !== -1) {
|
|
5512
|
+
const cycle = [...activePath.slice(cycleStart), attributeName];
|
|
5513
|
+
throw new ComputedFormulaCompileError(`Computed field cycle detected: ${cycle.join(" -> ")}`);
|
|
5514
|
+
}
|
|
5515
|
+
if (visited.has(attributeName)) {
|
|
5516
|
+
return;
|
|
5517
|
+
}
|
|
5518
|
+
const node = graph.get(attributeName);
|
|
5519
|
+
if (node === void 0) {
|
|
5520
|
+
return;
|
|
5521
|
+
}
|
|
5522
|
+
const nextPath = [...activePath, attributeName];
|
|
5523
|
+
for (const dependency of node.dependencies) {
|
|
5524
|
+
if (dependency.kind === "computed") {
|
|
5525
|
+
visit(dependency.attributeName, nextPath);
|
|
5526
|
+
}
|
|
5527
|
+
}
|
|
5528
|
+
visited.add(attributeName);
|
|
5529
|
+
};
|
|
5530
|
+
for (const node of nodes) {
|
|
5531
|
+
visit(node.attributeName, []);
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
5534
|
+
function parseCompileFormula(expression) {
|
|
5535
|
+
try {
|
|
5536
|
+
return parseComputedFormula(expression);
|
|
5537
|
+
} catch (error) {
|
|
5538
|
+
if (error instanceof Error) {
|
|
5539
|
+
throw new ComputedFormulaCompileError(error.message);
|
|
5540
|
+
}
|
|
5541
|
+
throw error;
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5544
|
+
function inferNode(node, schema) {
|
|
5545
|
+
switch (node.kind) {
|
|
5546
|
+
case "literal":
|
|
5547
|
+
return {
|
|
5548
|
+
valueType: literalToValueType(node.value),
|
|
5549
|
+
dependencies: [],
|
|
5550
|
+
...typeof node.value === "string" ? { literalString: node.value } : {}
|
|
5551
|
+
};
|
|
5552
|
+
case "path":
|
|
5553
|
+
return inferPath(node.parts, schema);
|
|
5554
|
+
case "call":
|
|
5555
|
+
return inferCall(node.functionName, node.args, schema);
|
|
5556
|
+
case "binary":
|
|
5557
|
+
return inferBinary(
|
|
5558
|
+
node.operator,
|
|
5559
|
+
inferNode(node.left, schema),
|
|
5560
|
+
inferNode(node.right, schema)
|
|
5561
|
+
);
|
|
5562
|
+
default: {
|
|
5563
|
+
const exhaustive = node;
|
|
5564
|
+
return exhaustive;
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
function literalToValueType(value) {
|
|
5569
|
+
switch (typeof value) {
|
|
5570
|
+
case "string":
|
|
5571
|
+
return { kind: "scalar", type: "text" };
|
|
5572
|
+
case "number":
|
|
5573
|
+
return { kind: "scalar", type: "number" };
|
|
5574
|
+
case "boolean":
|
|
5575
|
+
return { kind: "scalar", type: "boolean" };
|
|
5576
|
+
case "object":
|
|
5577
|
+
return { kind: "scalar", type: "text" };
|
|
5578
|
+
default: {
|
|
5579
|
+
const exhaustive = value;
|
|
5580
|
+
return exhaustive;
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
5584
|
+
function inferPath(parts, schema) {
|
|
5585
|
+
if (parts.length === 1) {
|
|
5586
|
+
const attributeName = parts[0];
|
|
5587
|
+
const attribute = findAttribute(schema, attributeName);
|
|
5588
|
+
if (attribute === void 0) {
|
|
5589
|
+
throw new ComputedFormulaCompileError(
|
|
5590
|
+
`Unknown attribute "${attributeName}" on object "${schema.objectName}"`
|
|
5591
|
+
);
|
|
5592
|
+
}
|
|
5593
|
+
if (attribute.type === "relation") {
|
|
5594
|
+
return {
|
|
5595
|
+
valueType: relationToValueType(),
|
|
5596
|
+
dependencies: [
|
|
5597
|
+
{
|
|
5598
|
+
kind: "relation",
|
|
5599
|
+
relationName: attribute.name,
|
|
5600
|
+
cardinality: relationCardinality(attribute)
|
|
5601
|
+
}
|
|
5602
|
+
],
|
|
5603
|
+
targetAttributeType: attribute.type
|
|
5604
|
+
};
|
|
5605
|
+
}
|
|
5606
|
+
return {
|
|
5607
|
+
valueType: attributeToValueType(attribute),
|
|
5608
|
+
dependencies: [localDependencyFor(attribute)],
|
|
5609
|
+
optionsSource: optionsSourceForAttribute(schema.objectName, attribute),
|
|
5610
|
+
targetAttributeType: attribute.type
|
|
5611
|
+
};
|
|
5612
|
+
}
|
|
5613
|
+
if (parts.length === 2) {
|
|
5614
|
+
const [relationName, targetAttributeName] = parts;
|
|
5615
|
+
const relation2 = findAttribute(schema, relationName);
|
|
5616
|
+
if (relation2 === void 0) {
|
|
5617
|
+
throw new ComputedFormulaCompileError(
|
|
5618
|
+
`Unknown attribute "${relationName}" on object "${schema.objectName}"`
|
|
5619
|
+
);
|
|
5620
|
+
}
|
|
5621
|
+
if (relation2.type !== "relation") {
|
|
5622
|
+
throw new ComputedFormulaCompileError(
|
|
5623
|
+
`Attribute "${relationName}" on object "${schema.objectName}" is not a relation`
|
|
5624
|
+
);
|
|
5625
|
+
}
|
|
5626
|
+
const targetObjectName = getRelationTargetObjectName(relation2);
|
|
5627
|
+
if (targetObjectName === void 0) {
|
|
5628
|
+
throw new ComputedFormulaCompileError(
|
|
5629
|
+
`Relation "${relationName}" on object "${schema.objectName}" is missing targetObjectName`
|
|
5630
|
+
);
|
|
5631
|
+
}
|
|
5632
|
+
const targetObject = schema.relatedObjects?.[targetObjectName];
|
|
5633
|
+
if (targetObject === void 0) {
|
|
5634
|
+
throw new ComputedFormulaCompileError(`Unknown related object "${targetObjectName}"`);
|
|
5635
|
+
}
|
|
5636
|
+
const targetAttribute = findAttribute(targetObject, targetAttributeName);
|
|
5637
|
+
if (targetAttribute === void 0) {
|
|
5638
|
+
throw new ComputedFormulaCompileError(
|
|
5639
|
+
`Unknown attribute "${targetAttributeName}" on object "${targetObject.objectName}"`
|
|
5640
|
+
);
|
|
5641
|
+
}
|
|
5642
|
+
const targetValueType = attributeToValueType(targetAttribute);
|
|
5643
|
+
return {
|
|
5644
|
+
valueType: relation2.cardinality === "many" ? asCollectionValueType(targetValueType) : targetValueType,
|
|
5645
|
+
dependencies: [
|
|
5646
|
+
{
|
|
5647
|
+
kind: "relation",
|
|
5648
|
+
relationName,
|
|
5649
|
+
targetAttributeName,
|
|
5650
|
+
cardinality: relationCardinality(relation2)
|
|
5651
|
+
}
|
|
5652
|
+
],
|
|
5653
|
+
optionsSource: optionsSourceForAttribute(targetObject.objectName, targetAttribute),
|
|
5654
|
+
targetAttributeType: targetAttribute.type
|
|
5655
|
+
};
|
|
5656
|
+
}
|
|
5657
|
+
throw new ComputedFormulaCompileError("Computed paths support at most one relation hop");
|
|
5658
|
+
}
|
|
5659
|
+
function findAttribute(object2, attributeName) {
|
|
5660
|
+
return object2.attributes.find((attribute) => attribute.name === attributeName);
|
|
5661
|
+
}
|
|
5662
|
+
function getRelationTargetObjectName(attribute) {
|
|
5663
|
+
if (attribute.targetObjectName !== void 0) {
|
|
5664
|
+
return attribute.targetObjectName;
|
|
5665
|
+
}
|
|
5666
|
+
if (attribute.targets?.length === 1) {
|
|
5667
|
+
const [target] = attribute.targets;
|
|
5668
|
+
return target?.object === "*" ? void 0 : target?.object;
|
|
5669
|
+
}
|
|
5670
|
+
return void 0;
|
|
5671
|
+
}
|
|
5672
|
+
function relationCardinality(attribute) {
|
|
5673
|
+
return attribute.cardinality ?? "one";
|
|
5674
|
+
}
|
|
5675
|
+
function localDependencyFor(attribute) {
|
|
5676
|
+
if (attribute.type === "formula" || attribute.type === "rollup") {
|
|
5677
|
+
return { kind: "computed", attributeName: attribute.name };
|
|
5678
|
+
}
|
|
5679
|
+
return { kind: "local", attributeName: attribute.name };
|
|
5680
|
+
}
|
|
5681
|
+
function attributeToValueType(attribute) {
|
|
5682
|
+
if (attribute.type === "formula" || attribute.type === "rollup") {
|
|
5683
|
+
if (attribute.returnType === void 0) {
|
|
5684
|
+
throw new ComputedFormulaCompileError(
|
|
5685
|
+
`Computed attribute "${attribute.name}" is missing returnType`
|
|
5686
|
+
);
|
|
5687
|
+
}
|
|
5688
|
+
return returnTypeToValueType(attribute.returnType);
|
|
5689
|
+
}
|
|
5690
|
+
switch (attribute.type) {
|
|
5691
|
+
case "number":
|
|
5692
|
+
case "currency":
|
|
5693
|
+
return { kind: "scalar", type: "number" };
|
|
5694
|
+
case "checkbox":
|
|
5695
|
+
return { kind: "scalar", type: "boolean" };
|
|
5696
|
+
case "date":
|
|
5697
|
+
return { kind: "scalar", type: "date" };
|
|
5698
|
+
case "select":
|
|
5699
|
+
case "status":
|
|
5700
|
+
return { kind: "scalar", type: "select" };
|
|
5701
|
+
case "multiselect":
|
|
5702
|
+
return { kind: "collection", itemType: "select" };
|
|
5703
|
+
case "text":
|
|
5704
|
+
case "richtext":
|
|
5705
|
+
case "phone":
|
|
5706
|
+
case "file":
|
|
5707
|
+
case "user":
|
|
5708
|
+
case "location":
|
|
5709
|
+
case "relation":
|
|
5710
|
+
case "document":
|
|
5711
|
+
return { kind: "scalar", type: "text" };
|
|
5712
|
+
default: {
|
|
5713
|
+
const exhaustive = attribute.type;
|
|
5714
|
+
return exhaustive;
|
|
5715
|
+
}
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
function relationToValueType() {
|
|
5719
|
+
return { kind: "collection", itemType: "text" };
|
|
5720
|
+
}
|
|
5721
|
+
function returnTypeToValueType(returnType) {
|
|
5722
|
+
if (returnType === "multiselect") {
|
|
5723
|
+
return { kind: "collection", itemType: "select" };
|
|
5724
|
+
}
|
|
5725
|
+
return { kind: "scalar", type: returnType };
|
|
5726
|
+
}
|
|
5727
|
+
function asCollectionValueType(valueType) {
|
|
5728
|
+
if (valueType.kind === "collection") {
|
|
5729
|
+
return valueType;
|
|
5730
|
+
}
|
|
5731
|
+
return { kind: "collection", itemType: valueType.type };
|
|
5732
|
+
}
|
|
5733
|
+
function optionsSourceForAttribute(objectName, attribute) {
|
|
5734
|
+
if (attribute.type === "formula" || attribute.type === "rollup") {
|
|
5735
|
+
if (!isSelectLikeReturnType(attribute.returnType)) {
|
|
5736
|
+
return void 0;
|
|
5737
|
+
}
|
|
5738
|
+
return attribute.optionsSource;
|
|
5739
|
+
}
|
|
5740
|
+
if (attribute.type !== "select" && attribute.type !== "status" && attribute.type !== "multiselect") {
|
|
5741
|
+
return void 0;
|
|
5742
|
+
}
|
|
5743
|
+
return attribute.optionsSource ?? {
|
|
5744
|
+
objectName,
|
|
5745
|
+
attributeName: attribute.name,
|
|
5746
|
+
...attribute.id ? { attributeId: attribute.id } : {}
|
|
5747
|
+
};
|
|
5748
|
+
}
|
|
5749
|
+
function inferCall(functionName, args, schema) {
|
|
5750
|
+
if (!isComputedFunctionName(functionName)) {
|
|
5751
|
+
throw new ComputedFormulaCompileError(`Unknown computed function "${functionName}"`);
|
|
5752
|
+
}
|
|
5753
|
+
const signature = COMPUTED_FUNCTIONS[functionName];
|
|
5754
|
+
if (args.length < signature.minArgs || signature.maxArgs !== void 0 && args.length > signature.maxArgs) {
|
|
5755
|
+
const expected = signature.maxArgs === void 0 || signature.maxArgs === signature.minArgs ? String(signature.minArgs) : `${signature.minArgs}-${signature.maxArgs}`;
|
|
5756
|
+
throw new ComputedFormulaCompileError(
|
|
5757
|
+
`Function "${functionName}" expects ${expected} arguments, received ${args.length}`
|
|
5758
|
+
);
|
|
5759
|
+
}
|
|
5760
|
+
const inferredArgs = args.map((arg) => inferNode(arg, schema));
|
|
5761
|
+
return inferFunctionReturn(functionName, inferredArgs);
|
|
5762
|
+
}
|
|
5763
|
+
function inferFunctionReturn(functionName, args) {
|
|
5764
|
+
const dependencies = uniqueDependencies(args.flatMap((arg) => arg.dependencies));
|
|
5765
|
+
switch (functionName) {
|
|
5766
|
+
case "if":
|
|
5767
|
+
return inferIfFunction(args, dependencies);
|
|
5768
|
+
case "concat":
|
|
5769
|
+
ensureNoCollections(args, `Function "${functionName}" does not accept collection values`);
|
|
5770
|
+
return { valueType: { kind: "scalar", type: "text" }, dependencies };
|
|
5771
|
+
case "sum":
|
|
5772
|
+
case "avg":
|
|
5773
|
+
ensureNumericCollectionOrScalar(functionName, args[0]);
|
|
5774
|
+
return { valueType: { kind: "scalar", type: "number" }, dependencies };
|
|
5775
|
+
case "count":
|
|
5776
|
+
case "count_empty":
|
|
5777
|
+
case "count_not_empty":
|
|
5778
|
+
case "count_unique":
|
|
5779
|
+
case "percent_empty":
|
|
5780
|
+
case "percent_not_empty":
|
|
5781
|
+
if (functionName === "count_unique") {
|
|
5782
|
+
ensureUniqueCountSource(args[0]);
|
|
5783
|
+
} else {
|
|
5784
|
+
ensureCollectionSource(functionName, args[0]);
|
|
5785
|
+
}
|
|
5786
|
+
return { valueType: { kind: "scalar", type: "number" }, dependencies };
|
|
5787
|
+
case "min":
|
|
5788
|
+
case "max":
|
|
5789
|
+
ensureMinMaxSource(functionName, args[0]);
|
|
5790
|
+
return {
|
|
5791
|
+
valueType: sourceItemValueType(args[0]),
|
|
5792
|
+
dependencies,
|
|
5793
|
+
optionsSource: args[0].optionsSource,
|
|
5794
|
+
targetAttributeType: args[0].targetAttributeType
|
|
5795
|
+
};
|
|
5796
|
+
case "earliest":
|
|
5797
|
+
case "latest":
|
|
5798
|
+
ensureDateCollectionOrScalar(functionName, args[0]);
|
|
5799
|
+
return {
|
|
5800
|
+
valueType: { kind: "scalar", type: "date" },
|
|
5801
|
+
dependencies,
|
|
5802
|
+
targetAttributeType: args[0].targetAttributeType
|
|
5803
|
+
};
|
|
5804
|
+
case "unique":
|
|
5805
|
+
return inferUniqueFunction(args[0], dependencies);
|
|
5806
|
+
case "first":
|
|
5807
|
+
return inferFirstFunction(args[0], dependencies);
|
|
5808
|
+
default: {
|
|
5809
|
+
const exhaustive = functionName;
|
|
5810
|
+
return exhaustive;
|
|
5811
|
+
}
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5814
|
+
function inferIfFunction(args, dependencies) {
|
|
5815
|
+
ensureScalarType(args[0].valueType, "boolean", 'Function "if" condition must be boolean');
|
|
5816
|
+
const thenValue = args[1];
|
|
5817
|
+
const elseValue = args[2];
|
|
5818
|
+
const valueType = resolveIfBranchValueType(thenValue, elseValue);
|
|
5819
|
+
const optionsSource = resolveIfOptionsSource(thenValue, elseValue);
|
|
5820
|
+
if (valueType === void 0) {
|
|
5821
|
+
throw new ComputedFormulaCompileError('Function "if" branches must return the same type');
|
|
5822
|
+
}
|
|
5823
|
+
return {
|
|
5824
|
+
valueType,
|
|
5825
|
+
dependencies,
|
|
5826
|
+
...optionsSource ? { optionsSource } : {},
|
|
5827
|
+
targetAttributeType: thenValue.targetAttributeType ?? elseValue.targetAttributeType
|
|
5828
|
+
};
|
|
5829
|
+
}
|
|
5830
|
+
function inferUniqueFunction(source, dependencies) {
|
|
5831
|
+
if (source.valueType.kind !== "collection" || source.valueType.itemType !== "select") {
|
|
5832
|
+
throw new ComputedFormulaCompileError('Function "unique" expects a select collection');
|
|
5833
|
+
}
|
|
5834
|
+
return {
|
|
5835
|
+
valueType: { kind: "collection", itemType: "select" },
|
|
5836
|
+
dependencies,
|
|
5837
|
+
optionsSource: source.optionsSource,
|
|
5838
|
+
targetAttributeType: source.targetAttributeType
|
|
5839
|
+
};
|
|
5840
|
+
}
|
|
5841
|
+
function inferFirstFunction(source, dependencies) {
|
|
5842
|
+
if (source.valueType.kind !== "collection") {
|
|
5843
|
+
throw new ComputedFormulaCompileError('Function "first" expects a collection');
|
|
5844
|
+
}
|
|
5845
|
+
return {
|
|
5846
|
+
valueType: { kind: "scalar", type: source.valueType.itemType },
|
|
5847
|
+
dependencies,
|
|
5848
|
+
optionsSource: source.optionsSource,
|
|
5849
|
+
targetAttributeType: source.targetAttributeType
|
|
5850
|
+
};
|
|
5851
|
+
}
|
|
5852
|
+
function inferBinary(operator, left, right) {
|
|
5853
|
+
const dependencies = uniqueDependencies([...left.dependencies, ...right.dependencies]);
|
|
5854
|
+
if (operator === "+" || operator === "-" || operator === "*" || operator === "/") {
|
|
5855
|
+
if (left.valueType.kind === "collection" || right.valueType.kind === "collection") {
|
|
5856
|
+
throw new ComputedFormulaCompileError(
|
|
5857
|
+
`Cannot use collection values with arithmetic operator "${operator}"`
|
|
5858
|
+
);
|
|
5859
|
+
}
|
|
5860
|
+
if (operator === "+" && (left.valueType.type === "text" || right.valueType.type === "text")) {
|
|
5861
|
+
return { valueType: { kind: "scalar", type: "text" }, dependencies };
|
|
5862
|
+
}
|
|
5863
|
+
ensureScalarType(left.valueType, "number", `Operator "${operator}" expects number operands`);
|
|
5864
|
+
ensureScalarType(right.valueType, "number", `Operator "${operator}" expects number operands`);
|
|
5865
|
+
return { valueType: { kind: "scalar", type: "number" }, dependencies };
|
|
5866
|
+
}
|
|
5867
|
+
if (left.valueType.kind === "collection" || right.valueType.kind === "collection") {
|
|
5868
|
+
throw new ComputedFormulaCompileError(
|
|
5869
|
+
`Cannot use collection values with comparison operator "${operator}"`
|
|
5870
|
+
);
|
|
5871
|
+
}
|
|
5872
|
+
if (operator === "==" || operator === "!=") {
|
|
5873
|
+
if (left.valueType.type !== right.valueType.type) {
|
|
5874
|
+
throw new ComputedFormulaCompileError(
|
|
5875
|
+
`Operator "${operator}" expects operands with the same scalar type`
|
|
5876
|
+
);
|
|
5877
|
+
}
|
|
5878
|
+
return { valueType: { kind: "scalar", type: "boolean" }, dependencies };
|
|
5879
|
+
}
|
|
5880
|
+
if ((left.valueType.type !== "number" || right.valueType.type !== "number") && (left.valueType.type !== "date" || right.valueType.type !== "date")) {
|
|
5881
|
+
throw new ComputedFormulaCompileError(
|
|
5882
|
+
`Operator "${operator}" expects number or date operands of the same type`
|
|
5883
|
+
);
|
|
5884
|
+
}
|
|
5885
|
+
return { valueType: { kind: "scalar", type: "boolean" }, dependencies };
|
|
5886
|
+
}
|
|
5887
|
+
function ensureNoCollections(args, message) {
|
|
5888
|
+
for (const arg of args) {
|
|
5889
|
+
if (arg.valueType.kind === "collection") {
|
|
5890
|
+
throw new ComputedFormulaCompileError(message);
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
}
|
|
5894
|
+
function ensureNumericCollectionOrScalar(functionName, expression) {
|
|
5895
|
+
const type = expression.valueType.kind === "collection" ? expression.valueType.itemType : expression.valueType.type;
|
|
5896
|
+
if (type !== "number") {
|
|
5897
|
+
throw new ComputedFormulaCompileError(`Function "${functionName}" expects number values`);
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
function ensureDateCollectionOrScalar(functionName, expression) {
|
|
5901
|
+
const type = expression.valueType.kind === "collection" ? expression.valueType.itemType : expression.valueType.type;
|
|
5902
|
+
if (type !== "date") {
|
|
5903
|
+
throw new ComputedFormulaCompileError(`Function "${functionName}" expects date values`);
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5906
|
+
function ensureMinMaxSource(functionName, expression) {
|
|
5907
|
+
if (expression.valueType.kind !== "collection") {
|
|
5908
|
+
throw new ComputedFormulaCompileError(`Function "${functionName}" expects a collection`);
|
|
5909
|
+
}
|
|
5910
|
+
const type = expression.valueType.itemType;
|
|
5911
|
+
if (type !== "number" && type !== "date") {
|
|
5912
|
+
throw new ComputedFormulaCompileError(
|
|
5913
|
+
`Function "${functionName}" expects number or date values`
|
|
5914
|
+
);
|
|
5915
|
+
}
|
|
5916
|
+
}
|
|
5917
|
+
function ensureCollectionSource(functionName, expression) {
|
|
5918
|
+
if (expression.valueType.kind !== "collection") {
|
|
5919
|
+
throw new ComputedFormulaCompileError(`Function "${functionName}" expects a collection`);
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5922
|
+
function ensureUniqueCountSource(expression) {
|
|
5923
|
+
if (expression.valueType.kind !== "collection") {
|
|
5924
|
+
throw new ComputedFormulaCompileError('Function "count_unique" expects a collection');
|
|
5925
|
+
}
|
|
5926
|
+
}
|
|
5927
|
+
function ensureScalarType(valueType, expectedType, message) {
|
|
5928
|
+
if (valueType.kind !== "scalar" || valueType.type !== expectedType) {
|
|
5929
|
+
throw new ComputedFormulaCompileError(message);
|
|
5930
|
+
}
|
|
5931
|
+
}
|
|
5932
|
+
function sourceItemValueType(source) {
|
|
5933
|
+
if (source.valueType.kind === "scalar") {
|
|
5934
|
+
return source.valueType;
|
|
5935
|
+
}
|
|
5936
|
+
return { kind: "scalar", type: source.valueType.itemType };
|
|
5937
|
+
}
|
|
5938
|
+
function sameValueType(left, right) {
|
|
5939
|
+
if (left.kind === "scalar") {
|
|
5940
|
+
return right.kind === "scalar" && left.type === right.type;
|
|
5941
|
+
}
|
|
5942
|
+
return right.kind === "collection" && left.itemType === right.itemType;
|
|
5943
|
+
}
|
|
5944
|
+
function resolveIfBranchValueType(thenValue, elseValue) {
|
|
5945
|
+
if (sameValueType(thenValue.valueType, elseValue.valueType)) {
|
|
5946
|
+
return thenValue.valueType;
|
|
5947
|
+
}
|
|
5948
|
+
if (isSelectScalarWithStringLiteral(thenValue, elseValue)) {
|
|
5949
|
+
return thenValue.valueType;
|
|
5950
|
+
}
|
|
5951
|
+
if (isSelectScalarWithStringLiteral(elseValue, thenValue)) {
|
|
5952
|
+
return elseValue.valueType;
|
|
5953
|
+
}
|
|
5954
|
+
return void 0;
|
|
5955
|
+
}
|
|
5956
|
+
function isSelectScalarWithStringLiteral(selectValue, literalValue) {
|
|
5957
|
+
return selectValue.valueType.kind === "scalar" && selectValue.valueType.type === "select" && literalValue.valueType.kind === "scalar" && literalValue.valueType.type === "text" && literalValue.literalString !== void 0;
|
|
5958
|
+
}
|
|
5959
|
+
function resolveIfOptionsSource(thenValue, elseValue) {
|
|
5960
|
+
const thenSource = thenValue.optionsSource;
|
|
5961
|
+
const elseSource = elseValue.optionsSource;
|
|
5962
|
+
if (thenSource !== void 0 && elseSource !== void 0 && !sameOptionsSource(thenSource, elseSource)) {
|
|
5963
|
+
throw new ComputedFormulaCompileError(
|
|
5964
|
+
'Function "if" branches use incompatible options sources'
|
|
5965
|
+
);
|
|
5966
|
+
}
|
|
5967
|
+
return thenSource ?? elseSource;
|
|
5968
|
+
}
|
|
5969
|
+
function sameOptionsSource(left, right) {
|
|
5970
|
+
return left.objectName === right.objectName && left.attributeName === right.attributeName && left.attributeId === right.attributeId;
|
|
5971
|
+
}
|
|
5972
|
+
function isSelectLikeReturnType(returnType) {
|
|
5973
|
+
return returnType === "select" || returnType === "multiselect";
|
|
5974
|
+
}
|
|
5975
|
+
function valueTypeToReturnType(valueType) {
|
|
5976
|
+
if (valueType.kind === "scalar") {
|
|
5977
|
+
return valueType.type;
|
|
5978
|
+
}
|
|
5979
|
+
if (valueType.itemType === "select") {
|
|
5980
|
+
return "multiselect";
|
|
5981
|
+
}
|
|
5982
|
+
throw new ComputedFormulaCompileError(
|
|
5983
|
+
`Collection item type "${valueType.itemType}" cannot be used as a computed return type`
|
|
5984
|
+
);
|
|
5985
|
+
}
|
|
5986
|
+
function uniqueDependencies(dependencies) {
|
|
5987
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5988
|
+
const unique = [];
|
|
5989
|
+
for (const dependency of dependencies) {
|
|
5990
|
+
const key = JSON.stringify(stableNormalize(dependency));
|
|
5991
|
+
if (!seen.has(key)) {
|
|
5992
|
+
seen.add(key);
|
|
5993
|
+
unique.push(dependency);
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5996
|
+
return unique;
|
|
5997
|
+
}
|
|
5998
|
+
function createDependenciesHash(input) {
|
|
5999
|
+
const sortedDependencies = input.dependencies.map((dependency) => stableNormalize(dependency)).sort((left, right) => compareStrings(JSON.stringify(left), JSON.stringify(right)));
|
|
6000
|
+
return JSON.stringify(
|
|
6001
|
+
stableNormalize({
|
|
6002
|
+
expression: input.expression,
|
|
6003
|
+
returnType: input.returnType,
|
|
6004
|
+
dependencies: sortedDependencies
|
|
6005
|
+
})
|
|
6006
|
+
);
|
|
6007
|
+
}
|
|
6008
|
+
function stableNormalize(value) {
|
|
6009
|
+
if (Array.isArray(value)) {
|
|
6010
|
+
return value.map((item) => stableNormalize(item));
|
|
6011
|
+
}
|
|
6012
|
+
if (value !== null && typeof value === "object") {
|
|
6013
|
+
return Object.fromEntries(
|
|
6014
|
+
Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => compareStrings(left, right)).map(([key, item]) => [key, stableNormalize(item)])
|
|
6015
|
+
);
|
|
6016
|
+
}
|
|
6017
|
+
return value;
|
|
6018
|
+
}
|
|
6019
|
+
function compareStrings(left, right) {
|
|
6020
|
+
if (left < right) {
|
|
6021
|
+
return -1;
|
|
6022
|
+
}
|
|
6023
|
+
if (left > right) {
|
|
6024
|
+
return 1;
|
|
6025
|
+
}
|
|
6026
|
+
return 0;
|
|
6027
|
+
}
|
|
6028
|
+
|
|
6029
|
+
// src/computed/rollup-compiler.ts
|
|
6030
|
+
function rollupToFormulaExpression(attr) {
|
|
6031
|
+
if (attr.function === "count") {
|
|
6032
|
+
return `count(${attr.relationAttribute})`;
|
|
6033
|
+
}
|
|
6034
|
+
return `${rollupFunctionToFormulaFunction(attr.function)}(${attr.relationAttribute}.${attr.targetAttribute})`;
|
|
6035
|
+
}
|
|
6036
|
+
function inferRollupReturnType(attr, schema) {
|
|
6037
|
+
if (attr.function === "count") {
|
|
6038
|
+
return "number";
|
|
6039
|
+
}
|
|
6040
|
+
const targetAttribute = findRollupTargetAttribute(attr, schema);
|
|
6041
|
+
switch (attr.function) {
|
|
6042
|
+
case "countValues":
|
|
6043
|
+
case "countEmpty":
|
|
6044
|
+
case "percentEmpty":
|
|
6045
|
+
case "percentNotEmpty":
|
|
6046
|
+
case "countUniqueValues":
|
|
6047
|
+
return "number";
|
|
6048
|
+
case "sum":
|
|
6049
|
+
case "avg":
|
|
6050
|
+
return "number";
|
|
6051
|
+
case "earliest":
|
|
6052
|
+
case "latest":
|
|
6053
|
+
return "date";
|
|
6054
|
+
case "original":
|
|
6055
|
+
if (!isSelectLikeAttributeType(targetAttribute.type)) {
|
|
6056
|
+
throw new ComputedFormulaCompileError(
|
|
6057
|
+
`Rollup "original" requires a select, status, or multiselect target`
|
|
6058
|
+
);
|
|
6059
|
+
}
|
|
6060
|
+
return "multiselect";
|
|
6061
|
+
default: {
|
|
6062
|
+
const exhaustive = attr.function;
|
|
6063
|
+
return exhaustive;
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
function compileRollupAttribute(attr, schema) {
|
|
6068
|
+
const expression = rollupToFormulaExpression(attr);
|
|
6069
|
+
const returnType = inferRollupReturnType(attr, schema);
|
|
6070
|
+
const plan = compileComputedFormula({
|
|
6071
|
+
attributeName: attr.name,
|
|
6072
|
+
expression,
|
|
6073
|
+
returnType,
|
|
6074
|
+
schema: schemaWithRollupRelationAsMany(attr, schema)
|
|
6075
|
+
});
|
|
6076
|
+
return {
|
|
6077
|
+
...plan,
|
|
6078
|
+
kind: "rollup"
|
|
6079
|
+
};
|
|
6080
|
+
}
|
|
6081
|
+
function schemaWithRollupRelationAsMany(attr, schema) {
|
|
6082
|
+
return {
|
|
6083
|
+
...schema,
|
|
6084
|
+
attributes: schema.attributes.map(
|
|
6085
|
+
(attribute) => attribute.name === attr.relationAttribute && attribute.type === "relation" ? { ...attribute, cardinality: "many" } : attribute
|
|
6086
|
+
)
|
|
6087
|
+
};
|
|
6088
|
+
}
|
|
6089
|
+
function rollupFunctionToFormulaFunction(functionName) {
|
|
6090
|
+
switch (functionName) {
|
|
6091
|
+
case "sum":
|
|
6092
|
+
case "avg":
|
|
6093
|
+
case "earliest":
|
|
6094
|
+
case "latest":
|
|
6095
|
+
return functionName;
|
|
6096
|
+
case "original":
|
|
6097
|
+
return "unique";
|
|
6098
|
+
case "countValues":
|
|
6099
|
+
return "count_not_empty";
|
|
6100
|
+
case "countEmpty":
|
|
6101
|
+
return "count_empty";
|
|
6102
|
+
case "percentEmpty":
|
|
6103
|
+
return "percent_empty";
|
|
6104
|
+
case "percentNotEmpty":
|
|
6105
|
+
return "percent_not_empty";
|
|
6106
|
+
case "countUniqueValues":
|
|
6107
|
+
return "count_unique";
|
|
6108
|
+
default: {
|
|
6109
|
+
const exhaustive = functionName;
|
|
6110
|
+
return exhaustive;
|
|
6111
|
+
}
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
6114
|
+
function findRollupTargetAttribute(attr, schema) {
|
|
6115
|
+
const relation2 = findAttribute2(schema, attr.relationAttribute);
|
|
6116
|
+
if (relation2 === void 0) {
|
|
6117
|
+
throw new ComputedFormulaCompileError(
|
|
6118
|
+
`Unknown relation attribute "${attr.relationAttribute}" on object "${schema.objectName}"`
|
|
6119
|
+
);
|
|
6120
|
+
}
|
|
6121
|
+
if (relation2.type !== "relation") {
|
|
6122
|
+
throw new ComputedFormulaCompileError(
|
|
6123
|
+
`Attribute "${attr.relationAttribute}" on object "${schema.objectName}" is not a relation`
|
|
6124
|
+
);
|
|
6125
|
+
}
|
|
6126
|
+
const targetObjectName = getRelationTargetObjectName2(relation2);
|
|
6127
|
+
if (targetObjectName === void 0) {
|
|
6128
|
+
throw new ComputedFormulaCompileError(
|
|
6129
|
+
`Relation "${attr.relationAttribute}" on object "${schema.objectName}" is missing targetObjectName`
|
|
6130
|
+
);
|
|
6131
|
+
}
|
|
6132
|
+
const targetObject = schema.relatedObjects?.[targetObjectName];
|
|
6133
|
+
if (targetObject === void 0) {
|
|
6134
|
+
throw new ComputedFormulaCompileError(`Unknown related object "${targetObjectName}"`);
|
|
6135
|
+
}
|
|
6136
|
+
const targetAttribute = findAttribute2(targetObject, attr.targetAttribute);
|
|
6137
|
+
if (targetAttribute === void 0) {
|
|
6138
|
+
throw new ComputedFormulaCompileError(
|
|
6139
|
+
`Unknown attribute "${attr.targetAttribute}" on object "${targetObject.objectName}"`
|
|
6140
|
+
);
|
|
6141
|
+
}
|
|
6142
|
+
return targetAttribute;
|
|
6143
|
+
}
|
|
6144
|
+
function findAttribute2(object2, attributeName) {
|
|
6145
|
+
return object2.attributes.find((attribute) => attribute.name === attributeName);
|
|
6146
|
+
}
|
|
6147
|
+
function getRelationTargetObjectName2(attribute) {
|
|
6148
|
+
if (attribute.targetObjectName !== void 0) {
|
|
6149
|
+
return attribute.targetObjectName;
|
|
6150
|
+
}
|
|
6151
|
+
if (attribute.targets?.length === 1) {
|
|
6152
|
+
const [target] = attribute.targets;
|
|
6153
|
+
return target?.object === "*" ? void 0 : target?.object;
|
|
6154
|
+
}
|
|
6155
|
+
return void 0;
|
|
6156
|
+
}
|
|
6157
|
+
function isSelectLikeAttributeType(type) {
|
|
6158
|
+
return type === "select" || type === "status" || type === "multiselect";
|
|
6159
|
+
}
|
|
6160
|
+
|
|
6161
|
+
// src/computed/evaluator.ts
|
|
6162
|
+
function evaluateAst(node, input) {
|
|
6163
|
+
switch (node.kind) {
|
|
6164
|
+
case "literal":
|
|
6165
|
+
return node.value;
|
|
6166
|
+
case "path":
|
|
6167
|
+
return resolvePath(input, node.parts);
|
|
6168
|
+
case "binary":
|
|
6169
|
+
return evaluateBinary(
|
|
6170
|
+
node.operator,
|
|
6171
|
+
evaluateAst(node.left, input),
|
|
6172
|
+
evaluateAst(node.right, input)
|
|
6173
|
+
);
|
|
6174
|
+
case "call":
|
|
6175
|
+
return evaluateCallNode(node.functionName, node.args, input);
|
|
6176
|
+
default:
|
|
6177
|
+
return unreachable(node);
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
function evaluateCallNode(functionName, args, input) {
|
|
6181
|
+
assertComputedFunctionName(functionName);
|
|
6182
|
+
validateArity(functionName, args.length);
|
|
6183
|
+
if (functionName === "if") {
|
|
6184
|
+
return evaluateAst(args[0], input) ? evaluateAst(args[1], input) : evaluateAst(args[2], input);
|
|
6185
|
+
}
|
|
6186
|
+
return evaluateCall(
|
|
6187
|
+
functionName,
|
|
6188
|
+
args.map((arg) => evaluateAst(arg, input))
|
|
6189
|
+
);
|
|
6190
|
+
}
|
|
6191
|
+
function resolvePath(input, parts) {
|
|
6192
|
+
return parts.reduce((value, part) => resolvePathPart(value, part), input);
|
|
6193
|
+
}
|
|
6194
|
+
function resolvePathPart(value, part) {
|
|
6195
|
+
if (Array.isArray(value)) {
|
|
6196
|
+
return value.map((item) => resolvePathPart(item, part));
|
|
6197
|
+
}
|
|
6198
|
+
if (!isPlainRecord(value)) {
|
|
6199
|
+
return null;
|
|
6200
|
+
}
|
|
6201
|
+
if (Object.hasOwn(value, part)) {
|
|
6202
|
+
return value[part] ?? null;
|
|
4656
6203
|
}
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
6204
|
+
const nestedValues = value.values;
|
|
6205
|
+
if (isPlainRecord(nestedValues) && Object.hasOwn(nestedValues, part)) {
|
|
6206
|
+
return nestedValues[part] ?? null;
|
|
4660
6207
|
}
|
|
4661
|
-
|
|
4662
|
-
|
|
6208
|
+
return null;
|
|
6209
|
+
}
|
|
6210
|
+
function evaluateBinary(operator, left, right) {
|
|
6211
|
+
switch (operator) {
|
|
6212
|
+
case "+":
|
|
6213
|
+
return add(left, right);
|
|
6214
|
+
case "-":
|
|
6215
|
+
return numericBinary(left, right, (leftNumber, rightNumber) => leftNumber - rightNumber);
|
|
6216
|
+
case "*":
|
|
6217
|
+
return numericBinary(left, right, (leftNumber, rightNumber) => leftNumber * rightNumber);
|
|
6218
|
+
case "/":
|
|
6219
|
+
return numericBinary(left, right, (leftNumber, rightNumber) => leftNumber / rightNumber);
|
|
6220
|
+
case ">":
|
|
6221
|
+
return compareWith(left, right, (result) => result > 0);
|
|
6222
|
+
case "<":
|
|
6223
|
+
return compareWith(left, right, (result) => result < 0);
|
|
6224
|
+
case ">=":
|
|
6225
|
+
return compareWith(left, right, (result) => result >= 0);
|
|
6226
|
+
case "<=":
|
|
6227
|
+
return compareWith(left, right, (result) => result <= 0);
|
|
6228
|
+
case "==":
|
|
6229
|
+
return equalScalar(left, right);
|
|
6230
|
+
case "!=":
|
|
6231
|
+
return !equalScalar(left, right);
|
|
6232
|
+
default:
|
|
6233
|
+
return unreachable(operator);
|
|
4663
6234
|
}
|
|
4664
|
-
return result;
|
|
4665
6235
|
}
|
|
4666
|
-
function
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
const preservedTabs = getPreservedDetailTabs(view, options);
|
|
4670
|
-
const mergedTabs = mergeDetailTabs(defaultView.config.tabs, preservedTabs);
|
|
4671
|
-
const config = {
|
|
4672
|
-
...defaultView.config,
|
|
4673
|
-
tabs: mergedTabs
|
|
4674
|
-
};
|
|
4675
|
-
const resetView = {
|
|
4676
|
-
...defaultView,
|
|
4677
|
-
config
|
|
4678
|
-
};
|
|
4679
|
-
if (preserveMetadata) {
|
|
4680
|
-
resetView.id = view.id;
|
|
4681
|
-
resetView.name = view.name;
|
|
4682
|
-
resetView.label = view.label;
|
|
4683
|
-
resetView.description = view.description;
|
|
4684
|
-
resetView.icon = view.icon;
|
|
6236
|
+
function add(left, right) {
|
|
6237
|
+
if (typeof left === "string" || typeof right === "string") {
|
|
6238
|
+
return `${left ?? ""}${right ?? ""}`;
|
|
4685
6239
|
}
|
|
4686
|
-
return
|
|
6240
|
+
return numericBinary(left, right, (leftNumber, rightNumber) => leftNumber + rightNumber);
|
|
4687
6241
|
}
|
|
4688
|
-
function
|
|
4689
|
-
const
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
resetView.label = view.label;
|
|
4698
|
-
resetView.description = view.description;
|
|
4699
|
-
resetView.icon = view.icon;
|
|
6242
|
+
function compareWith(left, right, predicate) {
|
|
6243
|
+
const result = compare(left, right);
|
|
6244
|
+
return result === null ? null : predicate(result);
|
|
6245
|
+
}
|
|
6246
|
+
function compare(left, right) {
|
|
6247
|
+
const leftNumber = toFiniteNumber(left);
|
|
6248
|
+
const rightNumber = toFiniteNumber(right);
|
|
6249
|
+
if (leftNumber !== null && rightNumber !== null) {
|
|
6250
|
+
return leftNumber - rightNumber;
|
|
4700
6251
|
}
|
|
4701
|
-
|
|
6252
|
+
const leftDate = toDate(left);
|
|
6253
|
+
const rightDate = toDate(right);
|
|
6254
|
+
return leftDate && rightDate ? leftDate.getTime() - rightDate.getTime() : null;
|
|
4702
6255
|
}
|
|
4703
|
-
function
|
|
4704
|
-
if (
|
|
4705
|
-
return
|
|
6256
|
+
function equalScalar(left, right) {
|
|
6257
|
+
if (left === right) {
|
|
6258
|
+
return true;
|
|
4706
6259
|
}
|
|
4707
|
-
if (
|
|
4708
|
-
return
|
|
6260
|
+
if (left === null || left === void 0 || right === null || right === void 0) {
|
|
6261
|
+
return false;
|
|
4709
6262
|
}
|
|
4710
|
-
|
|
6263
|
+
return compare(left, right) === 0;
|
|
4711
6264
|
}
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
if (tabId) {
|
|
4716
|
-
const tab = config.tabs.find((t) => t.id === tabId);
|
|
4717
|
-
if (tab) return tab;
|
|
6265
|
+
function assertComputedFunctionName(name) {
|
|
6266
|
+
if (!isComputedFunctionName(name)) {
|
|
6267
|
+
throw new Error(`Unknown computed function "${name}"`);
|
|
4718
6268
|
}
|
|
4719
|
-
return config.tabs.find((t) => t.default) ?? config.tabs[0];
|
|
4720
6269
|
}
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
this.forms = /* @__PURE__ */ new Map();
|
|
4726
|
-
}
|
|
4727
|
-
/**
|
|
4728
|
-
* Register a form definition (or array of definitions).
|
|
4729
|
-
* @throws Error if a form with the same name is already registered.
|
|
4730
|
-
*/
|
|
4731
|
-
register(formOrForms) {
|
|
4732
|
-
const forms = Array.isArray(formOrForms) ? formOrForms : [formOrForms];
|
|
4733
|
-
for (const f of forms) {
|
|
4734
|
-
if (this.forms.has(f.name)) {
|
|
4735
|
-
throw new Error(`[FormRegistry] Duplicate form: "${f.name}"`);
|
|
4736
|
-
}
|
|
4737
|
-
this.forms.set(f.name, f);
|
|
4738
|
-
}
|
|
4739
|
-
return this;
|
|
4740
|
-
}
|
|
4741
|
-
/**
|
|
4742
|
-
* Get a form definition by name.
|
|
4743
|
-
*/
|
|
4744
|
-
get(name) {
|
|
4745
|
-
return this.forms.get(name);
|
|
6270
|
+
function validateArity(functionName, actual) {
|
|
6271
|
+
const { minArgs, maxArgs } = COMPUTED_FUNCTIONS[functionName];
|
|
6272
|
+
if (actual >= minArgs && actual <= (maxArgs ?? Number.POSITIVE_INFINITY)) {
|
|
6273
|
+
return;
|
|
4746
6274
|
}
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
6275
|
+
const range = maxArgs === void 0 ? `at least ${minArgs}` : `between ${minArgs} and ${maxArgs}`;
|
|
6276
|
+
throw new Error(`Function "${functionName}" expects ${range} arguments, received ${actual}`);
|
|
6277
|
+
}
|
|
6278
|
+
function evaluateCall(functionName, args) {
|
|
6279
|
+
switch (functionName) {
|
|
6280
|
+
case "concat":
|
|
6281
|
+
return args.filter((arg) => arg !== null && arg !== void 0).map(String).join("");
|
|
6282
|
+
case "sum":
|
|
6283
|
+
return numericValues(args).reduce((total, value) => total + value, 0);
|
|
6284
|
+
case "avg": {
|
|
6285
|
+
const values = numericValues(args);
|
|
6286
|
+
return values.length > 0 ? values.reduce((total, value) => total + value, 0) / values.length : null;
|
|
6287
|
+
}
|
|
6288
|
+
case "count":
|
|
6289
|
+
return collectionItems(args[0]).length;
|
|
6290
|
+
case "min":
|
|
6291
|
+
return minMax(args, "min");
|
|
6292
|
+
case "max":
|
|
6293
|
+
return minMax(args, "max");
|
|
6294
|
+
case "earliest":
|
|
6295
|
+
return dateMinMax(args, "min");
|
|
6296
|
+
case "latest":
|
|
6297
|
+
return dateMinMax(args, "max");
|
|
6298
|
+
case "unique":
|
|
6299
|
+
return uniqueNonEmpty(collectionItems(args[0]));
|
|
6300
|
+
case "first":
|
|
6301
|
+
return collectionItems(args[0]).find((item) => item !== null && item !== void 0) ?? null;
|
|
6302
|
+
case "percent_empty":
|
|
6303
|
+
return percentByEmptyState(args[0], true);
|
|
6304
|
+
case "percent_not_empty":
|
|
6305
|
+
return percentByEmptyState(args[0], false);
|
|
6306
|
+
case "count_empty":
|
|
6307
|
+
return collectionItems(args[0]).filter(isEmptyValue).length;
|
|
6308
|
+
case "count_not_empty":
|
|
6309
|
+
return collectionItems(args[0]).filter((item) => !isEmptyValue(item)).length;
|
|
6310
|
+
case "count_unique":
|
|
6311
|
+
return uniqueNonEmpty(collectionItems(args[0])).length;
|
|
6312
|
+
default:
|
|
6313
|
+
return unreachable(functionName);
|
|
4752
6314
|
}
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
6315
|
+
}
|
|
6316
|
+
function unreachable(value) {
|
|
6317
|
+
return value;
|
|
6318
|
+
}
|
|
6319
|
+
function numericValues(values) {
|
|
6320
|
+
return flatten(values).map(toFiniteNumber).filter((value) => value !== null);
|
|
6321
|
+
}
|
|
6322
|
+
function numericBinary(left, right, operation) {
|
|
6323
|
+
const leftNumber = toFiniteNumber(left);
|
|
6324
|
+
const rightNumber = toFiniteNumber(right);
|
|
6325
|
+
if (leftNumber === null || rightNumber === null) {
|
|
6326
|
+
return null;
|
|
4758
6327
|
}
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
var DOS_PROTECTION_LIMITS = {
|
|
4762
|
-
MAX_EXPRESSION_LENGTH: 1e4,
|
|
4763
|
-
MAX_TOKEN_COUNT: 1e3,
|
|
4764
|
-
MAX_NESTING_DEPTH: 50
|
|
4765
|
-
};
|
|
4766
|
-
function createFormulaParser() {
|
|
4767
|
-
const parser = new Parser();
|
|
4768
|
-
parser.functions.IF = (condition, thenValue, elseValue) => condition ? thenValue : elseValue;
|
|
4769
|
-
parser.functions.AND = (...args) => args.every(Boolean);
|
|
4770
|
-
parser.functions.OR = (...args) => args.some(Boolean);
|
|
4771
|
-
parser.functions.NOT = (value) => !value;
|
|
4772
|
-
parser.functions.EMPTY = (value) => value === null || value === void 0 || value === "";
|
|
4773
|
-
parser.functions.COALESCE = (...args) => args.find((a) => a != null) ?? null;
|
|
4774
|
-
parser.functions.DEFAULT = (value, defaultValue) => value == null || value === "" ? defaultValue : value;
|
|
4775
|
-
parser.functions.CONCAT = (...args) => args.filter((a) => a != null).map(String).join("");
|
|
4776
|
-
parser.functions.UPPER = (value) => String(value ?? "").toUpperCase();
|
|
4777
|
-
parser.functions.LOWER = (value) => String(value ?? "").toLowerCase();
|
|
4778
|
-
parser.functions.TRIM = (value) => String(value ?? "").trim();
|
|
4779
|
-
parser.functions.LENGTH = (value) => String(value ?? "").length;
|
|
4780
|
-
parser.functions.LEFT = (value, count) => String(value ?? "").slice(0, count);
|
|
4781
|
-
parser.functions.RIGHT = (value, count) => String(value ?? "").slice(-count);
|
|
4782
|
-
parser.functions.REPLACE = (value, search, replacement) => String(value ?? "").split(String(search)).join(replacement);
|
|
4783
|
-
parser.functions.CONTAINS = (value, search) => String(value ?? "").toLowerCase().includes(String(search).toLowerCase());
|
|
4784
|
-
parser.functions.ROUND = (value, decimals = 0) => {
|
|
4785
|
-
if (typeof value !== "number" || Number.isNaN(value)) return null;
|
|
4786
|
-
const factor = 10 ** decimals;
|
|
4787
|
-
return Math.round(value * factor) / factor;
|
|
4788
|
-
};
|
|
4789
|
-
parser.functions.FLOOR = (value) => {
|
|
4790
|
-
if (typeof value !== "number" || Number.isNaN(value)) return null;
|
|
4791
|
-
return Math.floor(value);
|
|
4792
|
-
};
|
|
4793
|
-
parser.functions.CEIL = (value) => {
|
|
4794
|
-
if (typeof value !== "number" || Number.isNaN(value)) return null;
|
|
4795
|
-
return Math.ceil(value);
|
|
4796
|
-
};
|
|
4797
|
-
parser.functions.ABS = (value) => {
|
|
4798
|
-
if (typeof value !== "number" || Number.isNaN(value)) return null;
|
|
4799
|
-
return Math.abs(value);
|
|
4800
|
-
};
|
|
4801
|
-
parser.functions.MIN = (...args) => {
|
|
4802
|
-
const nums = args.filter((n) => typeof n === "number" && !Number.isNaN(n));
|
|
4803
|
-
return nums.length > 0 ? Math.min(...nums) : null;
|
|
4804
|
-
};
|
|
4805
|
-
parser.functions.MAX = (...args) => {
|
|
4806
|
-
const nums = args.filter((n) => typeof n === "number" && !Number.isNaN(n));
|
|
4807
|
-
return nums.length > 0 ? Math.max(...nums) : null;
|
|
4808
|
-
};
|
|
4809
|
-
parser.functions.POW = (base, exponent) => {
|
|
4810
|
-
if (typeof base !== "number" || typeof exponent !== "number") return null;
|
|
4811
|
-
if (exponent > 1e3 || exponent < -1e3) return null;
|
|
4812
|
-
return base ** exponent;
|
|
4813
|
-
};
|
|
4814
|
-
parser.functions.MOD = (a, b) => {
|
|
4815
|
-
if (typeof a !== "number" || typeof b !== "number" || b === 0) return null;
|
|
4816
|
-
return a % b;
|
|
4817
|
-
};
|
|
4818
|
-
parser.functions.NOW = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
4819
|
-
parser.functions.TODAY = () => (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4820
|
-
parser.functions.YEAR = (value) => {
|
|
4821
|
-
const date2 = parseDate(value);
|
|
4822
|
-
return date2 ? date2.getFullYear() : null;
|
|
4823
|
-
};
|
|
4824
|
-
parser.functions.MONTH = (value) => {
|
|
4825
|
-
const date2 = parseDate(value);
|
|
4826
|
-
return date2 ? date2.getMonth() + 1 : null;
|
|
4827
|
-
};
|
|
4828
|
-
parser.functions.DAY = (value) => {
|
|
4829
|
-
const date2 = parseDate(value);
|
|
4830
|
-
return date2 ? date2.getDate() : null;
|
|
4831
|
-
};
|
|
4832
|
-
parser.functions.DATE_DIFF = (date1, date2, unit = "days") => {
|
|
4833
|
-
const d1 = parseDate(date1);
|
|
4834
|
-
const d2 = parseDate(date2);
|
|
4835
|
-
if (d1 === null || d2 === null) return null;
|
|
4836
|
-
const diffMs = d1.getTime() - d2.getTime();
|
|
4837
|
-
const MS_PER_DAY = 1e3 * 60 * 60 * 24;
|
|
4838
|
-
const conversions = {
|
|
4839
|
-
years: MS_PER_DAY * 365,
|
|
4840
|
-
months: MS_PER_DAY * 30,
|
|
4841
|
-
weeks: MS_PER_DAY * 7,
|
|
4842
|
-
days: MS_PER_DAY,
|
|
4843
|
-
hours: 1e3 * 60 * 60,
|
|
4844
|
-
minutes: 1e3 * 60
|
|
4845
|
-
};
|
|
4846
|
-
const divisor = conversions[unit] ?? MS_PER_DAY;
|
|
4847
|
-
return diffMs / divisor;
|
|
4848
|
-
};
|
|
4849
|
-
return parser;
|
|
6328
|
+
const result = operation(leftNumber, rightNumber);
|
|
6329
|
+
return Number.isFinite(result) ? result : null;
|
|
4850
6330
|
}
|
|
4851
|
-
function
|
|
4852
|
-
|
|
4853
|
-
if (
|
|
4854
|
-
|
|
4855
|
-
const date2 = new Date(value);
|
|
4856
|
-
return Number.isNaN(date2.getTime()) ? null : date2;
|
|
6331
|
+
function minMax(values, mode) {
|
|
6332
|
+
const numbers = numericValues(values);
|
|
6333
|
+
if (numbers.length > 0) {
|
|
6334
|
+
return mode === "min" ? Math.min(...numbers) : Math.max(...numbers);
|
|
4857
6335
|
}
|
|
4858
|
-
return
|
|
6336
|
+
return dateMinMax(values, mode);
|
|
4859
6337
|
}
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
if (
|
|
4863
|
-
return
|
|
4864
|
-
valid: false,
|
|
4865
|
-
error: `Expression too long (${expression.length} chars, max ${DOS_PROTECTION_LIMITS.MAX_EXPRESSION_LENGTH})`
|
|
4866
|
-
};
|
|
6338
|
+
function dateMinMax(values, mode) {
|
|
6339
|
+
const dates = flatten(values).map(toDate).filter((date2) => date2 !== null);
|
|
6340
|
+
if (dates.length === 0) {
|
|
6341
|
+
return null;
|
|
4867
6342
|
}
|
|
4868
|
-
const
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
6343
|
+
const timestamps = dates.map((date2) => date2.getTime());
|
|
6344
|
+
const timestamp = mode === "min" ? Math.min(...timestamps) : Math.max(...timestamps);
|
|
6345
|
+
return new Date(timestamp).toISOString();
|
|
6346
|
+
}
|
|
6347
|
+
function percentByEmptyState(value, countEmpty) {
|
|
6348
|
+
const items = collectionItems(value);
|
|
6349
|
+
if (items.length === 0) {
|
|
6350
|
+
return 0;
|
|
4874
6351
|
}
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
6352
|
+
const count = items.filter((item) => isEmptyValue(item) === countEmpty).length;
|
|
6353
|
+
return count / items.length;
|
|
6354
|
+
}
|
|
6355
|
+
function uniqueNonEmpty(values) {
|
|
6356
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6357
|
+
const result = [];
|
|
6358
|
+
for (const value of values) {
|
|
6359
|
+
if (isEmptyValue(value)) {
|
|
6360
|
+
continue;
|
|
4883
6361
|
}
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
6362
|
+
const key = uniqueKey(value);
|
|
6363
|
+
if (seen.has(key)) {
|
|
6364
|
+
continue;
|
|
6365
|
+
}
|
|
6366
|
+
seen.add(key);
|
|
6367
|
+
result.push(value);
|
|
6368
|
+
}
|
|
6369
|
+
return result;
|
|
6370
|
+
}
|
|
6371
|
+
function collectionItems(value) {
|
|
6372
|
+
if (value === null || value === void 0) {
|
|
6373
|
+
return [];
|
|
4887
6374
|
}
|
|
6375
|
+
return Array.isArray(value) ? flatten(value) : [value];
|
|
4888
6376
|
}
|
|
4889
|
-
function
|
|
4890
|
-
|
|
4891
|
-
return tokens.length;
|
|
6377
|
+
function flatten(values) {
|
|
6378
|
+
return values.flat(Number.POSITIVE_INFINITY);
|
|
4892
6379
|
}
|
|
4893
|
-
function
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
6380
|
+
function isEmptyValue(value) {
|
|
6381
|
+
return value === null || value === void 0 || value === "";
|
|
6382
|
+
}
|
|
6383
|
+
function toFiniteNumber(value) {
|
|
6384
|
+
if (typeof value === "number") {
|
|
6385
|
+
return Number.isFinite(value) ? value : null;
|
|
6386
|
+
}
|
|
6387
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
6388
|
+
const parsed = Number(value);
|
|
6389
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
4903
6390
|
}
|
|
4904
|
-
return
|
|
6391
|
+
return null;
|
|
4905
6392
|
}
|
|
4906
|
-
function
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
return null;
|
|
4912
|
-
}
|
|
4913
|
-
const parsed = formulaParser.parse(expression);
|
|
4914
|
-
return parsed.evaluate(values);
|
|
4915
|
-
} catch {
|
|
6393
|
+
function toDate(value) {
|
|
6394
|
+
if (value instanceof Date) {
|
|
6395
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
6396
|
+
}
|
|
6397
|
+
if (typeof value !== "string") {
|
|
4916
6398
|
return null;
|
|
4917
6399
|
}
|
|
6400
|
+
const date2 = new Date(value);
|
|
6401
|
+
return Number.isNaN(date2.getTime()) ? null : date2;
|
|
4918
6402
|
}
|
|
4919
|
-
function
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
const value = parsed.evaluate(values);
|
|
4927
|
-
return { value };
|
|
4928
|
-
} catch (error) {
|
|
4929
|
-
return { value: null, error: getErrorMessage(error) };
|
|
6403
|
+
function uniqueKey(value) {
|
|
6404
|
+
const numberValue = toFiniteNumber(value);
|
|
6405
|
+
if (numberValue !== null) {
|
|
6406
|
+
return `number:${numberValue}`;
|
|
6407
|
+
}
|
|
6408
|
+
if (value instanceof Date) {
|
|
6409
|
+
return `date:${value.toISOString()}`;
|
|
4930
6410
|
}
|
|
6411
|
+
return `${typeof value}:${JSON.stringify(value)}`;
|
|
6412
|
+
}
|
|
6413
|
+
function isPlainRecord(value) {
|
|
6414
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4931
6415
|
}
|
|
4932
|
-
function
|
|
6416
|
+
function formatComputedResult(value, returnType, decimals) {
|
|
4933
6417
|
if (value === null || value === void 0) return null;
|
|
6418
|
+
const stringifyScalar = (input) => {
|
|
6419
|
+
if (typeof input === "string" || typeof input === "number" || typeof input === "boolean" || input instanceof Date) {
|
|
6420
|
+
return String(input);
|
|
6421
|
+
}
|
|
6422
|
+
return null;
|
|
6423
|
+
};
|
|
4934
6424
|
switch (returnType) {
|
|
4935
6425
|
case "number": {
|
|
4936
6426
|
const num = typeof value === "number" ? value : Number(value);
|
|
@@ -4945,152 +6435,163 @@ function formatFormulaResult(value, returnType, decimals) {
|
|
|
4945
6435
|
return null;
|
|
4946
6436
|
case "text":
|
|
4947
6437
|
return String(value);
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
function validateFormulaExpression(expression) {
|
|
4955
|
-
try {
|
|
4956
|
-
const complexityCheck = validateFormulaComplexity(expression);
|
|
4957
|
-
if (!complexityCheck.valid) return complexityCheck;
|
|
4958
|
-
formulaParser.parse(expression);
|
|
4959
|
-
return { valid: true };
|
|
4960
|
-
} catch (error) {
|
|
4961
|
-
return { valid: false, error: getErrorMessage(error) };
|
|
4962
|
-
}
|
|
4963
|
-
}
|
|
4964
|
-
function extractFormulaVariables(expression) {
|
|
4965
|
-
try {
|
|
4966
|
-
const parsed = formulaParser.parse(expression);
|
|
4967
|
-
return parsed.variables();
|
|
4968
|
-
} catch {
|
|
4969
|
-
return [];
|
|
4970
|
-
}
|
|
4971
|
-
}
|
|
4972
|
-
var RELATION_REF_PATTERN = /\b([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
|
|
4973
|
-
function extractRelationReferences(expression) {
|
|
4974
|
-
const regex = new RegExp(RELATION_REF_PATTERN.source, "g");
|
|
4975
|
-
const matches = expression.matchAll(regex);
|
|
4976
|
-
return [...matches].map((m) => m[0]);
|
|
4977
|
-
}
|
|
4978
|
-
function extractRelationNames(expression) {
|
|
4979
|
-
const refs = extractRelationReferences(expression);
|
|
4980
|
-
const names = refs.map((ref) => ref.split(".")[0]);
|
|
4981
|
-
return [...new Set(names)];
|
|
4982
|
-
}
|
|
4983
|
-
function hasRelationReferences(expression) {
|
|
4984
|
-
const regex = new RegExp(RELATION_REF_PATTERN.source);
|
|
4985
|
-
return regex.test(expression);
|
|
4986
|
-
}
|
|
4987
|
-
function flattenRelationsForEval(resolvedRelations) {
|
|
4988
|
-
const result = {};
|
|
4989
|
-
for (const [relationName, values] of Object.entries(resolvedRelations)) {
|
|
4990
|
-
result[relationName] = { ...values };
|
|
4991
|
-
}
|
|
4992
|
-
return result;
|
|
4993
|
-
}
|
|
4994
|
-
|
|
4995
|
-
// src/formula/path-parser.ts
|
|
4996
|
-
var InvalidPathError = class extends Error {
|
|
4997
|
-
constructor(path, segment, reason) {
|
|
4998
|
-
super(`Invalid path "${path}" at "${segment}": ${reason}`);
|
|
4999
|
-
this.path = path;
|
|
5000
|
-
this.segment = segment;
|
|
5001
|
-
this.reason = reason;
|
|
5002
|
-
this.name = "InvalidPathError";
|
|
5003
|
-
}
|
|
5004
|
-
};
|
|
5005
|
-
var MaxDepthExceededError = class extends Error {
|
|
5006
|
-
constructor(path, maxDepth) {
|
|
5007
|
-
super(`Path "${path}" exceeds maximum depth of ${maxDepth}`);
|
|
5008
|
-
this.path = path;
|
|
5009
|
-
this.maxDepth = maxDepth;
|
|
5010
|
-
this.name = "MaxDepthExceededError";
|
|
5011
|
-
}
|
|
5012
|
-
};
|
|
5013
|
-
async function parsePath(path, startSchema, getSchema, maxDepth = 5) {
|
|
5014
|
-
const segments = path.split(".");
|
|
5015
|
-
if (segments.length === 0 || segments.length === 1 && segments[0] === "") {
|
|
5016
|
-
throw new InvalidPathError(path, path, "Path cannot be empty");
|
|
5017
|
-
}
|
|
5018
|
-
if (segments.length > maxDepth) {
|
|
5019
|
-
throw new MaxDepthExceededError(path, maxDepth);
|
|
5020
|
-
}
|
|
5021
|
-
const result = [];
|
|
5022
|
-
let currentSchema = startSchema;
|
|
5023
|
-
for (let i = 0; i < segments.length; i++) {
|
|
5024
|
-
const segmentName = segments[i];
|
|
5025
|
-
const isLastSegment = i === segments.length - 1;
|
|
5026
|
-
const attr = currentSchema.attributes.find((a) => a.name === segmentName);
|
|
5027
|
-
if (!attr) {
|
|
5028
|
-
throw new InvalidPathError(
|
|
5029
|
-
path,
|
|
5030
|
-
segmentName,
|
|
5031
|
-
`Attribute "${segmentName}" not found in object "${currentSchema.name}"`
|
|
5032
|
-
);
|
|
5033
|
-
}
|
|
5034
|
-
if (attr.type === "relation") {
|
|
5035
|
-
const relationAttr = attr;
|
|
5036
|
-
const targetObject = relationAttr.targets[0]?.object;
|
|
5037
|
-
if (!targetObject) {
|
|
5038
|
-
throw new InvalidPathError(path, segmentName, "Relation has no target object");
|
|
5039
|
-
}
|
|
5040
|
-
result.push({
|
|
5041
|
-
name: segmentName,
|
|
5042
|
-
type: "relation",
|
|
5043
|
-
cardinality: relationAttr.cardinality,
|
|
5044
|
-
targetObject
|
|
5045
|
-
});
|
|
5046
|
-
if (!isLastSegment) {
|
|
5047
|
-
const nextSchema = await getSchema(targetObject);
|
|
5048
|
-
if (!nextSchema) {
|
|
5049
|
-
throw new InvalidPathError(
|
|
5050
|
-
path,
|
|
5051
|
-
segmentName,
|
|
5052
|
-
`Target object "${targetObject}" schema not found`
|
|
5053
|
-
);
|
|
6438
|
+
case "select":
|
|
6439
|
+
if (Array.isArray(value)) {
|
|
6440
|
+
for (const item of value) {
|
|
6441
|
+
if (item === null || item === void 0) continue;
|
|
6442
|
+
const formatted = stringifyScalar(item);
|
|
6443
|
+
if (formatted !== null) return formatted;
|
|
5054
6444
|
}
|
|
5055
|
-
|
|
6445
|
+
return null;
|
|
5056
6446
|
}
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
6447
|
+
return stringifyScalar(value);
|
|
6448
|
+
case "multiselect":
|
|
6449
|
+
if (Array.isArray(value)) {
|
|
6450
|
+
return value.flatMap((item) => {
|
|
6451
|
+
if (item === null || item === void 0) return [];
|
|
6452
|
+
const formatted = stringifyScalar(item);
|
|
6453
|
+
return formatted === null ? [] : [formatted];
|
|
6454
|
+
});
|
|
5064
6455
|
}
|
|
5065
|
-
|
|
6456
|
+
return stringifyScalar(value) === null ? [] : [String(value)];
|
|
6457
|
+
default: {
|
|
6458
|
+
const _exhaustive = returnType;
|
|
6459
|
+
throw new Error(`Unsupported computed return type: ${_exhaustive}`);
|
|
5066
6460
|
}
|
|
5067
6461
|
}
|
|
5068
|
-
return result;
|
|
5069
|
-
}
|
|
5070
|
-
async function validatePath(path, startSchema, getSchema, maxDepth = 5) {
|
|
5071
|
-
try {
|
|
5072
|
-
await parsePath(path, startSchema, getSchema, maxDepth);
|
|
5073
|
-
return true;
|
|
5074
|
-
} catch {
|
|
5075
|
-
return false;
|
|
5076
|
-
}
|
|
5077
|
-
}
|
|
5078
|
-
function pathHasManyCardinality(segments) {
|
|
5079
|
-
return segments.some((s) => s.type === "relation" && s.cardinality === "many");
|
|
5080
6462
|
}
|
|
5081
|
-
function
|
|
5082
|
-
return
|
|
6463
|
+
function evaluateComputedAst(node, input) {
|
|
6464
|
+
return evaluateAst(node, input);
|
|
5083
6465
|
}
|
|
5084
|
-
function
|
|
5085
|
-
|
|
5086
|
-
return parts[parts.length - 1];
|
|
6466
|
+
function evaluateComputedFormula(expression, input) {
|
|
6467
|
+
return evaluateAst(parseComputedFormula(expression), input);
|
|
5087
6468
|
}
|
|
5088
|
-
function
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
6469
|
+
function evaluateComputedFormulaWithResult(expression, input, returnType, decimals) {
|
|
6470
|
+
try {
|
|
6471
|
+
const raw = evaluateAst(parseComputedFormula(expression), input);
|
|
6472
|
+
const value = returnType ? formatComputedResult(raw, returnType, decimals) : raw;
|
|
6473
|
+
return { value };
|
|
6474
|
+
} catch (error) {
|
|
6475
|
+
return { value: null, error: error instanceof Error ? error.message : String(error) };
|
|
6476
|
+
}
|
|
5092
6477
|
}
|
|
5093
6478
|
|
|
6479
|
+
// src/computed/function-metadata.ts
|
|
6480
|
+
var COMPUTED_FUNCTION_METADATA = [
|
|
6481
|
+
{
|
|
6482
|
+
name: "if",
|
|
6483
|
+
signature: "if(condition, then, else)",
|
|
6484
|
+
description: "Conditional value",
|
|
6485
|
+
category: "logic",
|
|
6486
|
+
example: "if(amount > 100, 'big', 'small')"
|
|
6487
|
+
},
|
|
6488
|
+
{
|
|
6489
|
+
name: "concat",
|
|
6490
|
+
signature: "concat(a, b, ...)",
|
|
6491
|
+
description: "Join values into text",
|
|
6492
|
+
category: "text",
|
|
6493
|
+
example: "concat(firstName, ' ', lastName)"
|
|
6494
|
+
},
|
|
6495
|
+
{
|
|
6496
|
+
name: "sum",
|
|
6497
|
+
signature: "sum(collection)",
|
|
6498
|
+
description: "Total of values",
|
|
6499
|
+
category: "number",
|
|
6500
|
+
example: "sum(orders.amount)"
|
|
6501
|
+
},
|
|
6502
|
+
{
|
|
6503
|
+
name: "avg",
|
|
6504
|
+
signature: "avg(collection)",
|
|
6505
|
+
description: "Average of values",
|
|
6506
|
+
category: "number",
|
|
6507
|
+
example: "avg(orders.amount)"
|
|
6508
|
+
},
|
|
6509
|
+
{
|
|
6510
|
+
name: "min",
|
|
6511
|
+
signature: "min(collection)",
|
|
6512
|
+
description: "Smallest value",
|
|
6513
|
+
category: "number",
|
|
6514
|
+
example: "min(orders.amount)"
|
|
6515
|
+
},
|
|
6516
|
+
{
|
|
6517
|
+
name: "max",
|
|
6518
|
+
signature: "max(collection)",
|
|
6519
|
+
description: "Largest value",
|
|
6520
|
+
category: "number",
|
|
6521
|
+
example: "max(orders.amount)"
|
|
6522
|
+
},
|
|
6523
|
+
{
|
|
6524
|
+
name: "count",
|
|
6525
|
+
signature: "count(collection)",
|
|
6526
|
+
description: "Number of items",
|
|
6527
|
+
category: "collection",
|
|
6528
|
+
example: "count(orders)"
|
|
6529
|
+
},
|
|
6530
|
+
{
|
|
6531
|
+
name: "count_empty",
|
|
6532
|
+
signature: "count_empty(collection)",
|
|
6533
|
+
description: "Items with no value",
|
|
6534
|
+
category: "collection",
|
|
6535
|
+
example: "count_empty(orders.note)"
|
|
6536
|
+
},
|
|
6537
|
+
{
|
|
6538
|
+
name: "count_not_empty",
|
|
6539
|
+
signature: "count_not_empty(collection)",
|
|
6540
|
+
description: "Items with a value",
|
|
6541
|
+
category: "collection",
|
|
6542
|
+
example: "count_not_empty(orders.note)"
|
|
6543
|
+
},
|
|
6544
|
+
{
|
|
6545
|
+
name: "count_unique",
|
|
6546
|
+
signature: "count_unique(collection)",
|
|
6547
|
+
description: "Distinct values",
|
|
6548
|
+
category: "collection",
|
|
6549
|
+
example: "count_unique(orders.status)"
|
|
6550
|
+
},
|
|
6551
|
+
{
|
|
6552
|
+
name: "percent_empty",
|
|
6553
|
+
signature: "percent_empty(collection)",
|
|
6554
|
+
description: "Fraction with no value",
|
|
6555
|
+
category: "collection",
|
|
6556
|
+
example: "percent_empty(orders.note)"
|
|
6557
|
+
},
|
|
6558
|
+
{
|
|
6559
|
+
name: "percent_not_empty",
|
|
6560
|
+
signature: "percent_not_empty(collection)",
|
|
6561
|
+
description: "Fraction with a value",
|
|
6562
|
+
category: "collection",
|
|
6563
|
+
example: "percent_not_empty(orders.note)"
|
|
6564
|
+
},
|
|
6565
|
+
{
|
|
6566
|
+
name: "unique",
|
|
6567
|
+
signature: "unique(collection)",
|
|
6568
|
+
description: "Distinct values as a list",
|
|
6569
|
+
category: "collection",
|
|
6570
|
+
example: "unique(orders.status)"
|
|
6571
|
+
},
|
|
6572
|
+
{
|
|
6573
|
+
name: "first",
|
|
6574
|
+
signature: "first(collection)",
|
|
6575
|
+
description: "First non-empty value",
|
|
6576
|
+
category: "collection",
|
|
6577
|
+
example: "first(orders.status)"
|
|
6578
|
+
},
|
|
6579
|
+
{
|
|
6580
|
+
name: "earliest",
|
|
6581
|
+
signature: "earliest(collection)",
|
|
6582
|
+
description: "Earliest date",
|
|
6583
|
+
category: "date",
|
|
6584
|
+
example: "earliest(orders.closeDate)"
|
|
6585
|
+
},
|
|
6586
|
+
{
|
|
6587
|
+
name: "latest",
|
|
6588
|
+
signature: "latest(collection)",
|
|
6589
|
+
description: "Latest date",
|
|
6590
|
+
category: "date",
|
|
6591
|
+
example: "latest(orders.closeDate)"
|
|
6592
|
+
}
|
|
6593
|
+
];
|
|
6594
|
+
|
|
5094
6595
|
// src/filters/operators.ts
|
|
5095
6596
|
var isEmpty = (v) => v === null || v === void 0 || v === "" || Array.isArray(v) && v.length === 0;
|
|
5096
6597
|
var toStr = (v) => v == null ? "" : String(v);
|
|
@@ -5099,7 +6600,7 @@ var toNum = (v) => {
|
|
|
5099
6600
|
const n = typeof v === "number" ? v : Number(v);
|
|
5100
6601
|
return Number.isFinite(n) ? n : null;
|
|
5101
6602
|
};
|
|
5102
|
-
var
|
|
6603
|
+
var toDate2 = (v) => {
|
|
5103
6604
|
if (v == null) return null;
|
|
5104
6605
|
const d = v instanceof Date ? v : new Date(String(v));
|
|
5105
6606
|
return Number.isNaN(d.getTime()) ? null : d;
|
|
@@ -5118,48 +6619,38 @@ function relativeDateBoundary(rel) {
|
|
|
5118
6619
|
var OPERATOR_SPECS = {
|
|
5119
6620
|
// ---- text-style ----
|
|
5120
6621
|
is: {
|
|
5121
|
-
appliesTo: ["text", "
|
|
6622
|
+
appliesTo: ["text", "phone", "status", "select", "date", "formula"],
|
|
5122
6623
|
evaluateInMemory: (v, rv) => toStr(v) === toStr(rv)
|
|
5123
6624
|
},
|
|
5124
6625
|
is_not: {
|
|
5125
|
-
appliesTo: ["text", "
|
|
6626
|
+
appliesTo: ["text", "phone", "status", "select", "date", "formula"],
|
|
5126
6627
|
evaluateInMemory: (v, rv) => toStr(v) !== toStr(rv)
|
|
5127
6628
|
},
|
|
5128
6629
|
contains: {
|
|
5129
|
-
appliesTo: [
|
|
5130
|
-
"text",
|
|
5131
|
-
"textarea",
|
|
5132
|
-
"phone",
|
|
5133
|
-
"location",
|
|
5134
|
-
"user",
|
|
5135
|
-
"multiselect",
|
|
5136
|
-
"relation",
|
|
5137
|
-
"formula"
|
|
5138
|
-
],
|
|
6630
|
+
appliesTo: ["text", "phone", "location", "user", "multiselect", "relation", "formula"],
|
|
5139
6631
|
evaluateInMemory: (v, rv) => {
|
|
5140
6632
|
if (Array.isArray(v)) return v.map(toStr).includes(toStr(rv));
|
|
5141
6633
|
return toStr(v).toLowerCase().includes(toStr(rv).toLowerCase());
|
|
5142
6634
|
}
|
|
5143
6635
|
},
|
|
5144
6636
|
not_contains: {
|
|
5145
|
-
appliesTo: ["text", "
|
|
6637
|
+
appliesTo: ["text", "phone", "user", "multiselect", "relation", "formula"],
|
|
5146
6638
|
evaluateInMemory: (v, rv) => {
|
|
5147
6639
|
if (Array.isArray(v)) return !v.map(toStr).includes(toStr(rv));
|
|
5148
6640
|
return !toStr(v).toLowerCase().includes(toStr(rv).toLowerCase());
|
|
5149
6641
|
}
|
|
5150
6642
|
},
|
|
5151
6643
|
starts_with: {
|
|
5152
|
-
appliesTo: ["text", "
|
|
6644
|
+
appliesTo: ["text", "formula"],
|
|
5153
6645
|
evaluateInMemory: (v, rv) => toStr(v).toLowerCase().startsWith(toStr(rv).toLowerCase())
|
|
5154
6646
|
},
|
|
5155
6647
|
ends_with: {
|
|
5156
|
-
appliesTo: ["text", "
|
|
6648
|
+
appliesTo: ["text", "formula"],
|
|
5157
6649
|
evaluateInMemory: (v, rv) => toStr(v).toLowerCase().endsWith(toStr(rv).toLowerCase())
|
|
5158
6650
|
},
|
|
5159
6651
|
is_empty: {
|
|
5160
6652
|
appliesTo: [
|
|
5161
6653
|
"text",
|
|
5162
|
-
"textarea",
|
|
5163
6654
|
"richtext",
|
|
5164
6655
|
"number",
|
|
5165
6656
|
"phone",
|
|
@@ -5172,7 +6663,6 @@ var OPERATOR_SPECS = {
|
|
|
5172
6663
|
"file",
|
|
5173
6664
|
"user",
|
|
5174
6665
|
"relation",
|
|
5175
|
-
"rating",
|
|
5176
6666
|
"formula",
|
|
5177
6667
|
"document"
|
|
5178
6668
|
],
|
|
@@ -5181,7 +6671,6 @@ var OPERATOR_SPECS = {
|
|
|
5181
6671
|
is_not_empty: {
|
|
5182
6672
|
appliesTo: [
|
|
5183
6673
|
"text",
|
|
5184
|
-
"textarea",
|
|
5185
6674
|
"richtext",
|
|
5186
6675
|
"number",
|
|
5187
6676
|
"phone",
|
|
@@ -5194,7 +6683,6 @@ var OPERATOR_SPECS = {
|
|
|
5194
6683
|
"file",
|
|
5195
6684
|
"user",
|
|
5196
6685
|
"relation",
|
|
5197
|
-
"rating",
|
|
5198
6686
|
"formula",
|
|
5199
6687
|
"document"
|
|
5200
6688
|
],
|
|
@@ -5202,15 +6690,15 @@ var OPERATOR_SPECS = {
|
|
|
5202
6690
|
},
|
|
5203
6691
|
// ---- numeric ----
|
|
5204
6692
|
eq: {
|
|
5205
|
-
appliesTo: ["number", "currency", "
|
|
6693
|
+
appliesTo: ["number", "currency", "formula"],
|
|
5206
6694
|
evaluateInMemory: (v, rv) => toNum(v) === toNum(rv)
|
|
5207
6695
|
},
|
|
5208
6696
|
neq: {
|
|
5209
|
-
appliesTo: ["number", "currency", "
|
|
6697
|
+
appliesTo: ["number", "currency", "formula"],
|
|
5210
6698
|
evaluateInMemory: (v, rv) => toNum(v) !== toNum(rv)
|
|
5211
6699
|
},
|
|
5212
6700
|
lt: {
|
|
5213
|
-
appliesTo: ["number", "currency", "
|
|
6701
|
+
appliesTo: ["number", "currency", "formula"],
|
|
5214
6702
|
evaluateInMemory: (v, rv) => {
|
|
5215
6703
|
const a = toNum(v);
|
|
5216
6704
|
const b = toNum(rv);
|
|
@@ -5218,7 +6706,7 @@ var OPERATOR_SPECS = {
|
|
|
5218
6706
|
}
|
|
5219
6707
|
},
|
|
5220
6708
|
gt: {
|
|
5221
|
-
appliesTo: ["number", "currency", "
|
|
6709
|
+
appliesTo: ["number", "currency", "formula"],
|
|
5222
6710
|
evaluateInMemory: (v, rv) => {
|
|
5223
6711
|
const a = toNum(v);
|
|
5224
6712
|
const b = toNum(rv);
|
|
@@ -5226,7 +6714,7 @@ var OPERATOR_SPECS = {
|
|
|
5226
6714
|
}
|
|
5227
6715
|
},
|
|
5228
6716
|
lte: {
|
|
5229
|
-
appliesTo: ["number", "currency", "
|
|
6717
|
+
appliesTo: ["number", "currency", "formula"],
|
|
5230
6718
|
evaluateInMemory: (v, rv) => {
|
|
5231
6719
|
const a = toNum(v);
|
|
5232
6720
|
const b = toNum(rv);
|
|
@@ -5234,7 +6722,7 @@ var OPERATOR_SPECS = {
|
|
|
5234
6722
|
}
|
|
5235
6723
|
},
|
|
5236
6724
|
gte: {
|
|
5237
|
-
appliesTo: ["number", "currency", "
|
|
6725
|
+
appliesTo: ["number", "currency", "formula"],
|
|
5238
6726
|
evaluateInMemory: (v, rv) => {
|
|
5239
6727
|
const a = toNum(v);
|
|
5240
6728
|
const b = toNum(rv);
|
|
@@ -5254,39 +6742,39 @@ var OPERATOR_SPECS = {
|
|
|
5254
6742
|
before: {
|
|
5255
6743
|
appliesTo: ["date"],
|
|
5256
6744
|
evaluateInMemory: (v, rv) => {
|
|
5257
|
-
const a =
|
|
5258
|
-
const b =
|
|
6745
|
+
const a = toDate2(v);
|
|
6746
|
+
const b = toDate2(rv);
|
|
5259
6747
|
return a !== null && b !== null && a < b;
|
|
5260
6748
|
}
|
|
5261
6749
|
},
|
|
5262
6750
|
after: {
|
|
5263
6751
|
appliesTo: ["date"],
|
|
5264
6752
|
evaluateInMemory: (v, rv) => {
|
|
5265
|
-
const a =
|
|
5266
|
-
const b =
|
|
6753
|
+
const a = toDate2(v);
|
|
6754
|
+
const b = toDate2(rv);
|
|
5267
6755
|
return a !== null && b !== null && a > b;
|
|
5268
6756
|
}
|
|
5269
6757
|
},
|
|
5270
6758
|
on_or_before: {
|
|
5271
6759
|
appliesTo: ["date"],
|
|
5272
6760
|
evaluateInMemory: (v, rv) => {
|
|
5273
|
-
const a =
|
|
5274
|
-
const b =
|
|
6761
|
+
const a = toDate2(v);
|
|
6762
|
+
const b = toDate2(rv);
|
|
5275
6763
|
return a !== null && b !== null && a <= b;
|
|
5276
6764
|
}
|
|
5277
6765
|
},
|
|
5278
6766
|
on_or_after: {
|
|
5279
6767
|
appliesTo: ["date"],
|
|
5280
6768
|
evaluateInMemory: (v, rv) => {
|
|
5281
|
-
const a =
|
|
5282
|
-
const b =
|
|
6769
|
+
const a = toDate2(v);
|
|
6770
|
+
const b = toDate2(rv);
|
|
5283
6771
|
return a !== null && b !== null && a >= b;
|
|
5284
6772
|
}
|
|
5285
6773
|
},
|
|
5286
6774
|
is_within: {
|
|
5287
6775
|
appliesTo: ["date"],
|
|
5288
6776
|
evaluateInMemory: (v, rv) => {
|
|
5289
|
-
const d =
|
|
6777
|
+
const d = toDate2(v);
|
|
5290
6778
|
if (!d) return false;
|
|
5291
6779
|
const rel = rv;
|
|
5292
6780
|
const boundary = relativeDateBoundary(rel);
|
|
@@ -5296,7 +6784,7 @@ var OPERATOR_SPECS = {
|
|
|
5296
6784
|
day_month_eq: {
|
|
5297
6785
|
appliesTo: ["date"],
|
|
5298
6786
|
evaluateInMemory: (v, rv) => {
|
|
5299
|
-
const d =
|
|
6787
|
+
const d = toDate2(v);
|
|
5300
6788
|
if (!d) return false;
|
|
5301
6789
|
const rule = rv;
|
|
5302
6790
|
return d.getMonth() + 1 === rule.month && d.getDate() === rule.day;
|
|
@@ -5423,10 +6911,12 @@ function validateQualifiedRule(rule, context) {
|
|
|
5423
6911
|
return;
|
|
5424
6912
|
}
|
|
5425
6913
|
if (!rule.property) {
|
|
5426
|
-
|
|
5427
|
-
|
|
6914
|
+
const ops = getAttributeFilterOperators(rootAttr);
|
|
6915
|
+
if (!ops.includes(rule.operator)) {
|
|
6916
|
+
const validList = ops.length ? ops.join(", ") : "(not filterable)";
|
|
6917
|
+
const summary = `Operator "${rule.operator}" is not valid for attribute "${rootAttr.name}". Valid operators: ${validList}.`;
|
|
6918
|
+
throw new ValidationError(summary, [{ path: ["operator"], message: summary }]);
|
|
5428
6919
|
}
|
|
5429
|
-
assertOperatorForType(rule.operator, rootAttr.type, "attribute");
|
|
5430
6920
|
return;
|
|
5431
6921
|
}
|
|
5432
6922
|
if (!REFERENCE_TYPES.has(rootAttr.type)) {
|
|
@@ -5450,4 +6940,4 @@ function validateQualifiedRule(rule, context) {
|
|
|
5450
6940
|
assertOperatorForType(rule.operator, propDef.type, "property");
|
|
5451
6941
|
}
|
|
5452
6942
|
|
|
5453
|
-
export { ALLOWED_PROPERTY_TYPES, ALL_ACTIONS, ALL_SYSTEM_RESOURCES, ActivityTabConfig, BEHAVIOR_PROPERTIES, CustomTabConfig, DB_COLUMN_FIELDS, DEFAULT_DOCUMENT_SLOT, DEFAULT_ROLES, DEFAULT_ROLE_DESCRIPTIONS, DEFAULT_ROLE_LABELS, DEFAULT_ROLE_PERMISSIONS, DOCUMENT_SYSTEM_ATTRIBUTES, DetailViewBuilder, DocumentSlotValidationError, DocumentsTabConfig, EMPTY_VALUE_PLACEHOLDER, FORM_FORBIDDEN_ATTRIBUTE_TYPES, FlagRegistry, FlagService, FormBuilder, FormRegistry, FormRowBuilder, FormStepBuilder, GroupBuilder, IDENTITY_PROPERTIES,
|
|
6943
|
+
export { ALLOWED_PROPERTY_TYPES, ALL_ACTIONS, ALL_SYSTEM_RESOURCES, ATTRIBUTE_FILTER_OPERATORS, ActivityTabConfig, BEHAVIOR_PROPERTIES, COMPUTED_FUNCTIONS, COMPUTED_FUNCTION_METADATA, COMPUTED_FUNCTION_NAMES, ComputedFormulaCompileError, ComputedFormulaParseError, CustomTabConfig, DB_COLUMN_FIELDS, DEFAULT_DOCUMENT_SLOT, DEFAULT_ROLES, DEFAULT_ROLE_DESCRIPTIONS, DEFAULT_ROLE_LABELS, DEFAULT_ROLE_PERMISSIONS, DOCUMENT_SYSTEM_ATTRIBUTES, DetailViewBuilder, DocumentSlotValidationError, DocumentsTabConfig, EMPTY_VALUE_PLACEHOLDER, FORM_FORBIDDEN_ATTRIBUTE_TYPES, FlagRegistry, FlagService, FormBuilder, FormRegistry, FormRowBuilder, FormStepBuilder, GroupBuilder, IDENTITY_PROPERTIES, LIVE_EVENT_TYPES, LIVE_STREAM_START_CURSOR, ListViewBuilder, ListViewTabConfigBuilder, MAX_PRESET_DEPTH, NO_VALUE_OPERATORS, NoopGeocodingAdapter, OPERATORS_BY_TYPE, OPERATOR_SPECS, ObjectBuilder, PRESENTATION_PROPERTIES, QUALIFIED_SEPARATOR, RELATION_TARGET_ANY, RESERVED_ATTRIBUTE_NAMES, RelationGroupBuilder, RichtextTabConfig, STANDARD_SCHEMA_VENDOR, SYSTEM_ATTRIBUTES, SYSTEM_ATTRIBUTE_I18N_KEYS, SYSTEM_FIELD_NAMES, SYSTEM_RESOURCES, SYSTEM_RESOURCE_LABELS, TabBuilder, TableTabConfig, USER_STATUSES, accessLevelToActions, actionsToAccessLevel, applyPipes, assertAcyclicComputedDependencies, assertLiveStreamCursor, booleanFlag, buildPropertySchema, buildQualifiedAttribute, checkbox, compareLiveStreamCursors, compileComputedFormula, compileRollupAttribute, createFlagRegistry, createFlagService, createStandardSchemaProps, currency, date, detailView, document, evaluateComputedAst, evaluateComputedFormula, evaluateComputedFormulaWithResult, evaluateFilterState, extractAttributeNames, file, flagRegistry, form, formRegistry, formatAttributeValue, formatComputedResult, formula, generateDefaultDetailView, generateDefaultListView, getActiveTab, getAttributeCapabilities, getAttributeFilterOperators, getErrorMessage, getLiveProtocolPayloadChannel, getOperatorPolarity, getRollupFilterOperators2 as getRollupFilterOperators, getSystemAttributeI18nKey, getSystemAttributeList, group, hasOptions, inferInverseCardinality, inferRollupReturnType, isAttributeFilterable, isAttributeGroupable, isAttributeKanbanGroupable, isAttributeSearchable, isAttributeSortable2 as isAttributeSortable, isBilateralRelation, isComputedFunctionName, isDefaultRole, isDetailView, isDocumentAttribute, isEmptyObject, isFieldGroup, isFormDefinition, isFormFieldsRow, isFormFreeFieldRef, isFormHeadingRow, isFormSeparatorRow, isFormSlotFieldRef, isFormTextRow, isLabelExpression, isListView, isLiveErrorPayload, isLiveEventEnvelope, isLiveEventPayload, isLiveEventPayloadForType, isLiveEventType, isLiveGapPayload, isLiveReplayCompletePayload, isLiveStreamCursor, isNoValueOperator, isNotEmpty, isPlainRecord, isRelationGroup, isStandardSchema, isUniversalRelation, jsonFlag, listView, liveChannelKey, location, matchesMime, multiselect, normaliseDocumentSlots, normalizeForEdgeRpc, number, numberFlag, object, parseComputedFormula, parseLiveChannelKey, parseLiveSubscribePayload, parseLiveUnsubscribePayload, parseQualifiedAttribute, phone, registry, relation, relationGroup, renderLabelExpression, resetViewToDefault, richtext, rollup, rollupToFormulaExpression, select, status, stringFlag, text, toUndefinedIfEmpty, user, validateAttributeName, validateQualifiedRule, validateSlotAgainstConfig, viewRegistry };
|