@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.
Files changed (125) hide show
  1. package/dist/attributes-DP08iPCh.d.mts +889 -0
  2. package/dist/attributes-Ddhps_AU.d.ts +889 -0
  3. package/dist/{chunk-MAKSIK3P.js → chunk-5WATIVCA.js} +2 -2
  4. package/dist/{chunk-4KRLCWJM.mjs → chunk-6I2R22CX.mjs} +1 -1
  5. package/dist/{chunk-DC7YYE3U.js → chunk-ACKMC6FL.js} +2 -2
  6. package/dist/{chunk-TEQNVO7W.mjs → chunk-AYVHMVKQ.mjs} +1 -1
  7. package/dist/{chunk-P2BE2A2G.js → chunk-BV3IUMMW.js} +4 -7
  8. package/dist/{chunk-CCB4OY2O.js → chunk-E6JUYGJX.js} +2 -2
  9. package/dist/{chunk-7A3Y6O32.js → chunk-EBGRZUIH.js} +2 -2
  10. package/dist/{chunk-QWZHR2OO.js → chunk-EW5XR2X3.js} +2 -2
  11. package/dist/{chunk-UQVW4KPP.mjs → chunk-HRQEZUAA.mjs} +0 -1
  12. package/dist/{chunk-OAQWRMTP.js → chunk-HTSTPTNZ.js} +2 -2
  13. package/dist/{chunk-U4JC7P6D.mjs → chunk-HXDDJLHD.mjs} +37 -52
  14. package/dist/{chunk-Z75P63VF.mjs → chunk-JUKLL5RJ.mjs} +1 -1
  15. package/dist/{chunk-YQROI4IE.js → chunk-M5QIBMIH.js} +37 -52
  16. package/dist/{chunk-FQ2ZYOVS.js → chunk-MZOEL7FN.js} +47 -41
  17. package/dist/{chunk-KWVCGQ7N.mjs → chunk-NOMHDOVE.mjs} +2 -5
  18. package/dist/{chunk-OLJLCVNY.mjs → chunk-NS63SPNN.mjs} +1 -1
  19. package/dist/{chunk-J5HRK3WD.js → chunk-O44XVGHE.js} +2 -2
  20. package/dist/{chunk-TMYBEXBT.mjs → chunk-OSSGN43B.mjs} +1 -1
  21. package/dist/{chunk-LTHSU4NF.mjs → chunk-PMKCDRRO.mjs} +26 -21
  22. package/dist/{chunk-4TC27SMF.mjs → chunk-QEZUDVTN.mjs} +1 -1
  23. package/dist/{chunk-BLXQGQLK.mjs → chunk-QVLQPW3O.mjs} +1 -1
  24. package/dist/{chunk-EZHMVZQ4.js → chunk-QWKLQRBX.js} +4 -4
  25. package/dist/{chunk-ROAZLZA2.js → chunk-ROLWVTU3.js} +2 -2
  26. package/dist/{chunk-3JINFUN3.js → chunk-SGSGREPG.js} +2 -6
  27. package/dist/{chunk-VXOTYFEW.js → chunk-T6L2MUG2.js} +2 -2
  28. package/dist/{chunk-KNYZH2WD.mjs → chunk-UANQJ2GH.mjs} +8 -1
  29. package/dist/{chunk-IRTRJH37.mjs → chunk-UXAHLSWC.mjs} +1 -1
  30. package/dist/{chunk-ACQ3TUFK.js → chunk-V2UA2D4E.js} +2 -2
  31. package/dist/{chunk-R4CJHCC4.mjs → chunk-XZ5RG2OG.mjs} +1 -4
  32. package/dist/{chunk-TKN73433.js → chunk-YIP5FJ5H.js} +9 -2
  33. package/dist/{chunk-T225DB55.js → chunk-YKWSHBT5.js} +0 -1
  34. package/dist/{chunk-TBYXYSGA.mjs → chunk-YUFTHPOE.mjs} +1 -1
  35. package/dist/{chunk-ITTQ4FGR.mjs → chunk-ZDMOXF3U.mjs} +1 -1
  36. package/dist/{chunk-SMOHDR6M.mjs → chunk-ZDQ5AP26.mjs} +1 -1
  37. package/dist/{helpers-Y9rNQ6sz.d.ts → helpers-Bph8AoY7.d.mts} +15 -3
  38. package/dist/{helpers-HrKmSkLi.d.mts → helpers-D9klos6x.d.ts} +15 -3
  39. package/dist/index.d.mts +1121 -235
  40. package/dist/index.d.ts +1121 -235
  41. package/dist/index.js +2369 -854
  42. package/dist/index.mjs +2264 -774
  43. package/dist/{types-Bdlhgrf7.d.ts → types-D38kriFw.d.mts} +1 -2
  44. package/dist/{types-C5DittlR.d.mts → types-iKvYBe6v.d.ts} +1 -2
  45. package/dist/validation/all.d.mts +3 -3
  46. package/dist/validation/all.d.ts +3 -3
  47. package/dist/validation/all.js +30 -27
  48. package/dist/validation/all.mjs +17 -18
  49. package/dist/validation/complex/currency.d.mts +2 -2
  50. package/dist/validation/complex/currency.d.ts +2 -2
  51. package/dist/validation/complex/currency.js +3 -3
  52. package/dist/validation/complex/currency.mjs +2 -2
  53. package/dist/validation/complex/file.d.mts +2 -2
  54. package/dist/validation/complex/file.d.ts +2 -2
  55. package/dist/validation/complex/file.js +3 -3
  56. package/dist/validation/complex/file.mjs +2 -2
  57. package/dist/validation/complex/location.d.mts +2 -2
  58. package/dist/validation/complex/location.d.ts +2 -2
  59. package/dist/validation/complex/location.js +3 -3
  60. package/dist/validation/complex/location.mjs +2 -2
  61. package/dist/validation/complex/phone.d.mts +2 -2
  62. package/dist/validation/complex/phone.d.ts +2 -2
  63. package/dist/validation/complex/phone.js +3 -3
  64. package/dist/validation/complex/phone.mjs +2 -2
  65. package/dist/validation/complex/relation.d.mts +2 -2
  66. package/dist/validation/complex/relation.d.ts +2 -2
  67. package/dist/validation/complex/relation.js +5 -5
  68. package/dist/validation/complex/relation.mjs +2 -2
  69. package/dist/validation/complex/richtext.d.mts +2 -2
  70. package/dist/validation/complex/richtext.d.ts +2 -2
  71. package/dist/validation/complex/richtext.js +3 -3
  72. package/dist/validation/complex/richtext.mjs +2 -2
  73. package/dist/validation/complex/select.d.mts +2 -2
  74. package/dist/validation/complex/select.d.ts +2 -2
  75. package/dist/validation/complex/select.js +5 -5
  76. package/dist/validation/complex/select.mjs +2 -2
  77. package/dist/validation/complex/user.d.mts +2 -2
  78. package/dist/validation/complex/user.d.ts +2 -2
  79. package/dist/validation/complex/user.js +3 -3
  80. package/dist/validation/complex/user.mjs +2 -2
  81. package/dist/validation/computed/formula.d.mts +2 -2
  82. package/dist/validation/computed/formula.d.ts +2 -2
  83. package/dist/validation/computed/formula.js +3 -3
  84. package/dist/validation/computed/formula.mjs +2 -2
  85. package/dist/validation/computed/rollup.d.mts +2 -2
  86. package/dist/validation/computed/rollup.d.ts +2 -2
  87. package/dist/validation/computed/rollup.js +3 -3
  88. package/dist/validation/computed/rollup.mjs +2 -2
  89. package/dist/validation/config/index.d.mts +1 -1
  90. package/dist/validation/config/index.d.ts +1 -1
  91. package/dist/validation/config/index.js +4 -4
  92. package/dist/validation/config/index.mjs +1 -1
  93. package/dist/validation/core/index.d.mts +3 -3
  94. package/dist/validation/core/index.d.ts +3 -3
  95. package/dist/validation/core/index.js +3 -3
  96. package/dist/validation/core/index.mjs +1 -1
  97. package/dist/validation/object/index.d.mts +3 -3
  98. package/dist/validation/object/index.d.ts +3 -3
  99. package/dist/validation/object/index.js +32 -29
  100. package/dist/validation/object/index.mjs +16 -17
  101. package/dist/validation/primitives/checkbox.d.mts +2 -2
  102. package/dist/validation/primitives/checkbox.d.ts +2 -2
  103. package/dist/validation/primitives/checkbox.js +3 -3
  104. package/dist/validation/primitives/checkbox.mjs +2 -2
  105. package/dist/validation/primitives/date.d.mts +2 -2
  106. package/dist/validation/primitives/date.d.ts +2 -2
  107. package/dist/validation/primitives/date.js +3 -3
  108. package/dist/validation/primitives/date.mjs +2 -2
  109. package/dist/validation/primitives/number.d.mts +2 -2
  110. package/dist/validation/primitives/number.d.ts +2 -2
  111. package/dist/validation/primitives/number.js +3 -3
  112. package/dist/validation/primitives/number.mjs +2 -2
  113. package/dist/validation/primitives/text.d.mts +3 -8
  114. package/dist/validation/primitives/text.d.ts +3 -8
  115. package/dist/validation/primitives/text.js +3 -7
  116. package/dist/validation/primitives/text.mjs +2 -2
  117. package/package.json +2 -13
  118. package/dist/chunk-D7WFIGXW.js +0 -14
  119. package/dist/chunk-RUYUFXNW.mjs +0 -12
  120. package/dist/filters-DVBn5tov.d.ts +0 -1630
  121. package/dist/filters-HDG4kLoo.d.mts +0 -1630
  122. package/dist/validation/primitives/rating.d.mts +0 -12
  123. package/dist/validation/primitives/rating.d.ts +0 -12
  124. package/dist/validation/primitives/rating.js +0 -11
  125. 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-U4JC7P6D.mjs';
5
- import { createObjectValidator, SchemaError, SchemaErrorCode, ValidationError, createAttributeValidator } from './chunk-LTHSU4NF.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, validateDraft, validateDraftOrThrow, validateObject, validateObjectOrThrow } from './chunk-LTHSU4NF.mjs';
7
- import './chunk-TMYBEXBT.mjs';
8
- import './chunk-4KRLCWJM.mjs';
9
- import './chunk-KNYZH2WD.mjs';
10
- import './chunk-RUYUFXNW.mjs';
11
- import './chunk-KWVCGQ7N.mjs';
12
- import './chunk-BLXQGQLK.mjs';
13
- import './chunk-SMOHDR6M.mjs';
14
- import './chunk-ITTQ4FGR.mjs';
15
- import './chunk-IRTRJH37.mjs';
16
- import './chunk-OLJLCVNY.mjs';
17
- import './chunk-TEQNVO7W.mjs';
18
- import './chunk-4TC27SMF.mjs';
19
- import './chunk-TBYXYSGA.mjs';
20
- import { formatPhoneForDisplay } from './chunk-Z75P63VF.mjs';
21
- import './chunk-R4CJHCC4.mjs';
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
- var NON_SORTABLE_TYPES = /* @__PURE__ */ new Set([
54
- "richtext",
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 getRollupFilterOperators(attr) {
184
- const fn = attr.function;
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(options, attributeName) {
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 "${option.id}" in "${attributeName}" has an empty or missing value.`
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.id}" in "${attributeName}" has an empty or missing label.`
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(mutableOptions, this.attr.name ?? "unknown");
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(allOptions, this.attr.name ?? "unknown");
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(mutableOptions, this.attr.name ?? "unknown");
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(allOptions, this.attr.name ?? "unknown");
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(mutableOptions, this.attr.name ?? "unknown");
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(allOptions, this.attr.name ?? "unknown");
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
- allowedRoles(roles) {
1598
- this.attr.allowedRoles = roles;
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 getVisibleAttributes(attributes, excludeNames = []) {
4580
+ function getRuntimeAttributes(attributes, excludeNames = []) {
4515
4581
  return attributes.filter(
4516
- (attr) => !(attr.hidden || attr.archived || attr.system || excludeNames.includes(attr.name))
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(type) {
4528
- switch (type) {
4529
- case "textarea":
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.type)
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 visibleAttrs = getVisibleAttributes(object2.attributes, excludeAttributes);
4553
- const sortedAttrs = sortAttributes(visibleAttrs);
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 visibleAttrs = getVisibleAttributes(object2.attributes, excludeAttributes);
4609
- const sortedAttrs = sortAttributes(visibleAttrs);
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
- let order = 1;
4658
- for (const tab of preservedTabs) {
4659
- result.push({ ...tab, order: order++ });
6204
+ const nestedValues = value.values;
6205
+ if (isPlainRecord(nestedValues) && Object.hasOwn(nestedValues, part)) {
6206
+ return nestedValues[part] ?? null;
4660
6207
  }
4661
- for (const tab of otherTabs) {
4662
- result.push({ ...tab, order: order++ });
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 resetDetailViewToDefault(view, object2, options) {
4667
- const { preserveMetadata = true, ...generateOptions } = options;
4668
- const defaultView = generateDefaultDetailView(object2, generateOptions);
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 resetView;
6240
+ return numericBinary(left, right, (leftNumber, rightNumber) => leftNumber + rightNumber);
4687
6241
  }
4688
- function resetListViewToDefault(view, object2, options) {
4689
- const { preserveMetadata = true, ...generateOptions } = options;
4690
- const defaultView = generateDefaultListView(object2, generateOptions);
4691
- const resetView = {
4692
- ...defaultView
4693
- };
4694
- if (preserveMetadata) {
4695
- resetView.id = view.id;
4696
- resetView.name = view.name;
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
- return resetView;
6252
+ const leftDate = toDate(left);
6253
+ const rightDate = toDate(right);
6254
+ return leftDate && rightDate ? leftDate.getTime() - rightDate.getTime() : null;
4702
6255
  }
4703
- function resetViewToDefault(currentView, object2, options = {}) {
4704
- if (isDetailView(currentView)) {
4705
- return resetDetailViewToDefault(currentView, object2, options);
6256
+ function equalScalar(left, right) {
6257
+ if (left === right) {
6258
+ return true;
4706
6259
  }
4707
- if (isListView(currentView)) {
4708
- return resetListViewToDefault(currentView, object2, options);
6260
+ if (left === null || left === void 0 || right === null || right === void 0) {
6261
+ return false;
4709
6262
  }
4710
- throw new Error(`Reset not supported for view type: ${currentView.type}`);
6263
+ return compare(left, right) === 0;
4711
6264
  }
4712
-
4713
- // src/views/index.ts
4714
- function getActiveTab(config, tabId) {
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
- // src/forms/registry.ts
4723
- var FormRegistry = class {
4724
- constructor() {
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
- * Get all registered form definitions.
4749
- */
4750
- getAll() {
4751
- return Array.from(this.forms.values());
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
- * List all registered form names.
4755
- */
4756
- listNames() {
4757
- return Array.from(this.forms.keys());
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
- var formRegistry = new FormRegistry();
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 parseDate(value) {
4852
- if (!value) return null;
4853
- if (value instanceof Date) return value;
4854
- if (typeof value === "string" || typeof value === "number") {
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 null;
6336
+ return dateMinMax(values, mode);
4859
6337
  }
4860
- var formulaParser = createFormulaParser();
4861
- function validateFormulaComplexity(expression) {
4862
- if (expression.length > DOS_PROTECTION_LIMITS.MAX_EXPRESSION_LENGTH) {
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 nestingDepth = calculateNestingDepth(expression);
4869
- if (nestingDepth > DOS_PROTECTION_LIMITS.MAX_NESTING_DEPTH) {
4870
- return {
4871
- valid: false,
4872
- error: `Expression too deeply nested (${nestingDepth} levels, max ${DOS_PROTECTION_LIMITS.MAX_NESTING_DEPTH})`
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
- try {
4876
- formulaParser.parse(expression);
4877
- const tokenCountApprox = estimateTokenCount(expression);
4878
- if (tokenCountApprox > DOS_PROTECTION_LIMITS.MAX_TOKEN_COUNT) {
4879
- return {
4880
- valid: false,
4881
- error: `Expression too complex (approx ${tokenCountApprox} tokens, max ${DOS_PROTECTION_LIMITS.MAX_TOKEN_COUNT})`
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
- return { valid: true };
4885
- } catch {
4886
- return { valid: true };
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 estimateTokenCount(expression) {
4890
- const tokens = expression.match(/-?\d+\.?\d*|[a-zA-Z_][a-zA-Z0-9_]*|[+\-*/(),.><=!&|]+|'(?:[^'\\]|\\.)*'/g) ?? [];
4891
- return tokens.length;
6377
+ function flatten(values) {
6378
+ return values.flat(Number.POSITIVE_INFINITY);
4892
6379
  }
4893
- function calculateNestingDepth(expression) {
4894
- let maxDepth = 0;
4895
- let currentDepth = 0;
4896
- for (const char of expression) {
4897
- if (char === "(") {
4898
- currentDepth++;
4899
- if (currentDepth > maxDepth) maxDepth = currentDepth;
4900
- } else if (char === ")") {
4901
- currentDepth--;
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 maxDepth;
6391
+ return null;
4905
6392
  }
4906
- function evaluateFormula(expression, values) {
4907
- try {
4908
- const complexityCheck = validateFormulaComplexity(expression);
4909
- if (!complexityCheck.valid) {
4910
- console.warn(`[formula] Complexity limit violated: ${complexityCheck.error}`);
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 evaluateFormulaWithResult(expression, values) {
4920
- try {
4921
- const complexityCheck = validateFormulaComplexity(expression);
4922
- if (!complexityCheck.valid) {
4923
- return { value: null, error: complexityCheck.error };
4924
- }
4925
- const parsed = formulaParser.parse(expression);
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 formatFormulaResult(value, returnType, decimals) {
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
- function evaluateFormulaAttribute(attr, values) {
4951
- const raw = evaluateFormula(attr.expression, values);
4952
- return formatFormulaResult(raw, attr.returnType, attr.decimals);
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
- currentSchema = nextSchema;
6445
+ return null;
5056
6446
  }
5057
- } else {
5058
- if (!isLastSegment) {
5059
- throw new InvalidPathError(
5060
- path,
5061
- segmentName,
5062
- `"${segmentName}" is not a relation but has segments after it`
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
- result.push({ name: segmentName, type: "attribute" });
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 getPathDepth(segments) {
5082
- return segments.filter((s) => s.type === "relation").length;
6463
+ function evaluateComputedAst(node, input) {
6464
+ return evaluateAst(node, input);
5083
6465
  }
5084
- function getTargetAttributeName(path) {
5085
- const parts = path.split(".");
5086
- return parts[parts.length - 1];
6466
+ function evaluateComputedFormula(expression, input) {
6467
+ return evaluateAst(parseComputedFormula(expression), input);
5087
6468
  }
5088
- function getRelationPath(path) {
5089
- const parts = path.split(".");
5090
- if (parts.length <= 1) return null;
5091
- return parts.slice(0, -1).join(".");
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 toDate = (v) => {
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", "textarea", "phone", "status", "select", "date", "formula"],
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", "textarea", "phone", "status", "select", "date", "formula"],
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", "textarea", "phone", "user", "multiselect", "relation", "formula"],
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", "textarea", "formula"],
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", "textarea", "formula"],
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", "rating", "formula"],
6693
+ appliesTo: ["number", "currency", "formula"],
5206
6694
  evaluateInMemory: (v, rv) => toNum(v) === toNum(rv)
5207
6695
  },
5208
6696
  neq: {
5209
- appliesTo: ["number", "currency", "rating", "formula"],
6697
+ appliesTo: ["number", "currency", "formula"],
5210
6698
  evaluateInMemory: (v, rv) => toNum(v) !== toNum(rv)
5211
6699
  },
5212
6700
  lt: {
5213
- appliesTo: ["number", "currency", "rating", "formula"],
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", "rating", "formula"],
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", "rating", "formula"],
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", "rating", "formula"],
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 = toDate(v);
5258
- const b = toDate(rv);
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 = toDate(v);
5266
- const b = toDate(rv);
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 = toDate(v);
5274
- const b = toDate(rv);
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 = toDate(v);
5282
- const b = toDate(rv);
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 = toDate(v);
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 = toDate(v);
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
- if (rootAttr.type === "rollup") {
5427
- return;
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, InvalidPathError, ListViewBuilder, ListViewTabConfigBuilder, MAX_PRESET_DEPTH, MaxDepthExceededError, 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, booleanFlag, buildPropertySchema, buildQualifiedAttribute, checkbox, createFlagRegistry, createFlagService, createStandardSchemaProps, currency, date, detailView, document, evaluateFilterState, evaluateFormula, evaluateFormulaAttribute, evaluateFormulaWithResult, extractAttributeNames, extractFormulaVariables, extractRelationNames, extractRelationReferences, file, flagRegistry, flattenRelationsForEval, form, formRegistry, formatAttributeValue, formatFormulaResult, formula, generateDefaultDetailView, generateDefaultListView, getActiveTab, getErrorMessage, getOperatorPolarity, getPathDepth, getRelationPath, getRollupFilterOperators, getSystemAttributeI18nKey, getSystemAttributeList, getTargetAttributeName, group, hasOptions, hasRelationReferences, inferInverseCardinality, isAttributeSortable, isBilateralRelation, isDefaultRole, isDetailView, isDocumentAttribute, isEmptyObject, isFieldGroup, isFormDefinition, isFormFieldsRow, isFormFreeFieldRef, isFormHeadingRow, isFormSeparatorRow, isFormSlotFieldRef, isFormTextRow, isLabelExpression, isListView, isNoValueOperator, isNotEmpty, isRelationGroup, isStandardSchema, isUniversalRelation, jsonFlag, listView, location, matchesMime, multiselect, normaliseDocumentSlots, normalizeForEdgeRpc, number, numberFlag, object, parsePath, parseQualifiedAttribute, pathHasManyCardinality, phone, rating, registry, relation, relationGroup, renderLabelExpression, resetViewToDefault, richtext, rollup, select, status, stringFlag, text, textarea, toUndefinedIfEmpty, user, validateAttributeName, validateFormulaExpression, validatePath, validateQualifiedRule, validateSlotAgainstConfig, viewRegistry };
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 };