@dinoxx/dinox-cli 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +4 -0
- package/dist/commands/domain/options.d.ts +10 -0
- package/dist/commands/domain/options.js +75 -0
- package/dist/commands/domain/run.d.ts +24 -0
- package/dist/commands/domain/run.js +137 -0
- package/dist/commands/notes/actions.js +47 -18
- package/dist/commands/notes/createFromTemplate.d.ts +35 -0
- package/dist/commands/notes/createFromTemplate.js +58 -0
- package/dist/commands/notes/descriptors.js +9 -4
- package/dist/commands/notes/repo/read.js +4 -23
- package/dist/commands/notes/repo/write.d.ts +2 -0
- package/dist/commands/notes/repo/write.js +1 -1
- package/dist/commands/properties/index.d.ts +16 -0
- package/dist/commands/properties/index.js +640 -0
- package/dist/commands/properties/repo.d.ts +150 -0
- package/dist/commands/properties/repo.js +272 -0
- package/dist/commands/savedViews/fields.d.ts +28 -0
- package/dist/commands/savedViews/fields.js +87 -0
- package/dist/commands/savedViews/index.js +203 -164
- package/dist/commands/savedViews/model.d.ts +27 -69
- package/dist/commands/savedViews/model.js +63 -279
- package/dist/commands/savedViews/repo.d.ts +64 -62
- package/dist/commands/savedViews/repo.js +309 -428
- package/dist/commands/schema/renderSkills.js +29 -5
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/templates/index.d.ts +4 -0
- package/dist/commands/templates/index.js +290 -0
- package/dist/commands/templates/model.d.ts +12 -34
- package/dist/commands/templates/model.js +39 -83
- package/dist/commands/templates/repo.d.ts +53 -0
- package/dist/commands/templates/repo.js +78 -0
- package/dist/skills/registry.js +7 -0
- package/dist/utils/errorCodes.d.ts +212 -0
- package/dist/utils/errorCodes.js +53 -0
- package/dist/vendor/electron/domain/note-templates.d.ts +3 -0
- package/dist/vendor/electron/domain/note-templates.js +232 -0
- package/dist/vendor/electron/domain/properties.d.ts +10 -0
- package/dist/vendor/electron/domain/properties.js +1538 -0
- package/dist/vendor/electron/domain/saved-view-analytics.d.ts +45 -0
- package/dist/vendor/electron/domain/saved-view-analytics.js +365 -0
- package/dist/vendor/electron/domain/saved-view-relative-date.d.ts +21 -0
- package/dist/vendor/electron/domain/saved-view-relative-date.js +153 -0
- package/dist/vendor/electron/domain/saved-views.d.ts +61 -0
- package/dist/vendor/electron/domain/saved-views.js +1117 -0
- package/dist/vendor/electron/rpc.d.ts +22 -0
- package/dist/vendor/electron/rpc.js +27 -0
- package/dist/vendor/electron/shared/ai-errors.d.ts +11 -0
- package/dist/vendor/electron/shared/ai-errors.js +107 -0
- package/dist/vendor/electron/shared/external-types.d.ts +2 -0
- package/dist/vendor/electron/shared/external-types.js +1 -0
- package/dist/vendor/electron/shared/index.d.ts +9 -0
- package/dist/vendor/electron/shared/index.js +7 -0
- package/dist/vendor/electron/shared/note-properties.d.ts +312 -0
- package/dist/vendor/electron/shared/note-properties.js +982 -0
- package/dist/vendor/electron/shared/note-templates.d.ts +82 -0
- package/dist/vendor/electron/shared/note-templates.js +84 -0
- package/dist/vendor/electron/shared/powersync-read-models.d.ts +1710 -0
- package/dist/vendor/electron/shared/powersync-read-models.js +1 -0
- package/dist/vendor/electron/shared/saved-views.d.ts +394 -0
- package/dist/vendor/electron/shared/saved-views.js +981 -0
- package/dist/vendor/electron/sql-utils.d.ts +5 -0
- package/dist/vendor/electron/sql-utils.js +32 -0
- package/package.json +6 -3
- package/dist/commands/savedViews/semantics.d.ts +0 -46
- package/dist/commands/savedViews/semantics.js +0 -738
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
// Generated by scripts/sync-electron-domain.ts from dinox-electron/packages/shared/src/saved-views.ts. Do not edit manually.
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { isPropertyKeyValid, } from './note-properties.js';
|
|
4
|
+
export const SAVED_VIEW_DISPLAY_ONLY_SYSTEM_FIELDS = [
|
|
5
|
+
'cover',
|
|
6
|
+
'audio',
|
|
7
|
+
'video',
|
|
8
|
+
'file',
|
|
9
|
+
];
|
|
10
|
+
export const SAVED_VIEW_SYSTEM_FIELDS = [
|
|
11
|
+
'title',
|
|
12
|
+
'created_at',
|
|
13
|
+
'updated_at',
|
|
14
|
+
'tags',
|
|
15
|
+
'zettel_boxes',
|
|
16
|
+
'source',
|
|
17
|
+
'type',
|
|
18
|
+
'primary_resource_type',
|
|
19
|
+
'starred_at',
|
|
20
|
+
'images',
|
|
21
|
+
...SAVED_VIEW_DISPLAY_ONLY_SYSTEM_FIELDS,
|
|
22
|
+
];
|
|
23
|
+
/** Host note id. Valid only as a `$self` value source, never as a column. */
|
|
24
|
+
export const SAVED_VIEW_SELF_ID_FIELD = 'sys.id';
|
|
25
|
+
export const SAVED_VIEW_PRIMARY_RESOURCE_TYPES = [
|
|
26
|
+
'audio',
|
|
27
|
+
'video',
|
|
28
|
+
'file',
|
|
29
|
+
'image',
|
|
30
|
+
];
|
|
31
|
+
const savedViewSystemFieldRefs = new Set(SAVED_VIEW_SYSTEM_FIELDS.map((field) => `sys.${field}`));
|
|
32
|
+
const savedViewDisplayOnlyFieldRefs = new Set(SAVED_VIEW_DISPLAY_ONLY_SYSTEM_FIELDS.map((field) => `sys.${field}`));
|
|
33
|
+
export function isSavedViewDisplayOnlyField(field) {
|
|
34
|
+
return savedViewDisplayOnlyFieldRefs.has(field);
|
|
35
|
+
}
|
|
36
|
+
export function isSavedViewFieldRef(value) {
|
|
37
|
+
if (typeof value !== 'string')
|
|
38
|
+
return false;
|
|
39
|
+
if (savedViewSystemFieldRefs.has(value))
|
|
40
|
+
return true;
|
|
41
|
+
return value.startsWith('prop.') && isPropertyKeyValid(value.slice('prop.'.length));
|
|
42
|
+
}
|
|
43
|
+
export function isSavedViewSelfSourceFieldRef(value) {
|
|
44
|
+
return value === SAVED_VIEW_SELF_ID_FIELD || isSavedViewFieldRef(value);
|
|
45
|
+
}
|
|
46
|
+
export const savedViewFieldRefSchema = z.custom(isSavedViewFieldRef, 'Invalid saved view field reference');
|
|
47
|
+
export const SAVED_VIEW_FILTER_OPERATORS = [
|
|
48
|
+
'equals',
|
|
49
|
+
'notEquals',
|
|
50
|
+
'contains',
|
|
51
|
+
'notContains',
|
|
52
|
+
'greaterThan',
|
|
53
|
+
'greaterThanOrEqual',
|
|
54
|
+
'lessThan',
|
|
55
|
+
'lessThanOrEqual',
|
|
56
|
+
'isEmpty',
|
|
57
|
+
'isNotEmpty',
|
|
58
|
+
'withinLastDays',
|
|
59
|
+
'isToday',
|
|
60
|
+
'isThisWeek',
|
|
61
|
+
'isThisMonth',
|
|
62
|
+
];
|
|
63
|
+
export const MAX_SAVED_VIEW_RELATIVE_DAYS = 3650;
|
|
64
|
+
export const SAVED_VIEW_DYNAMIC_DATE_FILTER_OPERATORS = [
|
|
65
|
+
'withinLastDays',
|
|
66
|
+
'isToday',
|
|
67
|
+
'isThisWeek',
|
|
68
|
+
'isThisMonth',
|
|
69
|
+
];
|
|
70
|
+
const savedViewDynamicDateFilterOperators = new Set(SAVED_VIEW_DYNAMIC_DATE_FILTER_OPERATORS);
|
|
71
|
+
export function isSavedViewDynamicDateFilterOperator(operator) {
|
|
72
|
+
return savedViewDynamicDateFilterOperators.has(operator);
|
|
73
|
+
}
|
|
74
|
+
const TEXT_FILTER_OPERATORS = [
|
|
75
|
+
'equals',
|
|
76
|
+
'notEquals',
|
|
77
|
+
'contains',
|
|
78
|
+
'notContains',
|
|
79
|
+
'isEmpty',
|
|
80
|
+
'isNotEmpty',
|
|
81
|
+
];
|
|
82
|
+
const ORDERED_FILTER_OPERATORS = [
|
|
83
|
+
'equals',
|
|
84
|
+
'notEquals',
|
|
85
|
+
'greaterThan',
|
|
86
|
+
'greaterThanOrEqual',
|
|
87
|
+
'lessThan',
|
|
88
|
+
'lessThanOrEqual',
|
|
89
|
+
'isEmpty',
|
|
90
|
+
'isNotEmpty',
|
|
91
|
+
];
|
|
92
|
+
const DATE_FILTER_OPERATORS = [
|
|
93
|
+
'withinLastDays',
|
|
94
|
+
'isToday',
|
|
95
|
+
'isThisWeek',
|
|
96
|
+
'isThisMonth',
|
|
97
|
+
...ORDERED_FILTER_OPERATORS,
|
|
98
|
+
];
|
|
99
|
+
const LIST_FILTER_OPERATORS = [
|
|
100
|
+
'contains',
|
|
101
|
+
'notContains',
|
|
102
|
+
'isEmpty',
|
|
103
|
+
'isNotEmpty',
|
|
104
|
+
];
|
|
105
|
+
const SELECT_FILTER_OPERATORS = [
|
|
106
|
+
'equals',
|
|
107
|
+
'notEquals',
|
|
108
|
+
'isEmpty',
|
|
109
|
+
'isNotEmpty',
|
|
110
|
+
];
|
|
111
|
+
export function getSavedViewFilterOperatorsForPropertyType(type) {
|
|
112
|
+
if (type === 'files' || type === 'place')
|
|
113
|
+
return ['isEmpty', 'isNotEmpty'];
|
|
114
|
+
if (type === 'checkbox' ||
|
|
115
|
+
type === 'select' ||
|
|
116
|
+
type === 'status' ||
|
|
117
|
+
type === 'relation') {
|
|
118
|
+
return SELECT_FILTER_OPERATORS;
|
|
119
|
+
}
|
|
120
|
+
if (type === 'multi_select')
|
|
121
|
+
return LIST_FILTER_OPERATORS;
|
|
122
|
+
if (type === 'date')
|
|
123
|
+
return DATE_FILTER_OPERATORS;
|
|
124
|
+
if (type === 'number' || type === 'unique_id')
|
|
125
|
+
return ORDERED_FILTER_OPERATORS;
|
|
126
|
+
return TEXT_FILTER_OPERATORS;
|
|
127
|
+
}
|
|
128
|
+
const PRESENCE_FILTER_OPERATORS = ['isNotEmpty', 'isEmpty'];
|
|
129
|
+
export function getSavedViewFilterOperatorsForField(field, type) {
|
|
130
|
+
if (isSavedViewDisplayOnlyField(field))
|
|
131
|
+
return [];
|
|
132
|
+
if (field === 'sys.starred_at' || field === 'sys.images') {
|
|
133
|
+
return PRESENCE_FILTER_OPERATORS;
|
|
134
|
+
}
|
|
135
|
+
if (field === 'sys.primary_resource_type') {
|
|
136
|
+
return ['equals', 'notEquals', 'isEmpty', 'isNotEmpty'];
|
|
137
|
+
}
|
|
138
|
+
return getSavedViewFilterOperatorsForPropertyType(type);
|
|
139
|
+
}
|
|
140
|
+
export function canSortSavedViewPropertyType(type) {
|
|
141
|
+
return (type !== 'multi_select' &&
|
|
142
|
+
type !== 'relation' &&
|
|
143
|
+
type !== 'files' &&
|
|
144
|
+
type !== 'place');
|
|
145
|
+
}
|
|
146
|
+
export function canSortSavedViewField(field, type) {
|
|
147
|
+
return (field !== 'sys.starred_at' &&
|
|
148
|
+
field !== 'sys.primary_resource_type' &&
|
|
149
|
+
field !== 'sys.images' &&
|
|
150
|
+
!isSavedViewDisplayOnlyField(field) &&
|
|
151
|
+
canSortSavedViewPropertyType(type));
|
|
152
|
+
}
|
|
153
|
+
export function dedupeSavedViewFieldRefs(fields) {
|
|
154
|
+
return [...new Set(fields)];
|
|
155
|
+
}
|
|
156
|
+
export function normalizeSavedViewColumnFieldRefs(fields) {
|
|
157
|
+
return [
|
|
158
|
+
'sys.title',
|
|
159
|
+
...dedupeSavedViewFieldRefs(fields).filter(field => field !== 'sys.title'),
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
const MAX_SAVED_VIEW_FILTER_DEPTH = 8;
|
|
163
|
+
const MAX_SAVED_VIEW_FILTER_NODES = 100;
|
|
164
|
+
const MAX_SAVED_VIEW_FILTER_VALUE_LENGTH = 2000;
|
|
165
|
+
const savedViewFilterValueSchema = z.union([
|
|
166
|
+
z.string().max(MAX_SAVED_VIEW_FILTER_VALUE_LENGTH),
|
|
167
|
+
z.number(),
|
|
168
|
+
z.boolean(),
|
|
169
|
+
z.null(),
|
|
170
|
+
]);
|
|
171
|
+
const SAVED_VIEW_VALUE_REQUIRED_OPERATORS = new Set([
|
|
172
|
+
'contains',
|
|
173
|
+
'notContains',
|
|
174
|
+
'greaterThan',
|
|
175
|
+
'greaterThanOrEqual',
|
|
176
|
+
'lessThan',
|
|
177
|
+
'lessThanOrEqual',
|
|
178
|
+
'withinLastDays',
|
|
179
|
+
]);
|
|
180
|
+
const SAVED_VIEW_VALUELESS_OPERATORS = new Set([
|
|
181
|
+
'isEmpty',
|
|
182
|
+
'isNotEmpty',
|
|
183
|
+
'isToday',
|
|
184
|
+
'isThisWeek',
|
|
185
|
+
'isThisMonth',
|
|
186
|
+
]);
|
|
187
|
+
const SELF_VALUE_BLOCKED_CONDITION_FIELDS = new Set([
|
|
188
|
+
'sys.starred_at',
|
|
189
|
+
'sys.primary_resource_type',
|
|
190
|
+
'sys.zettel_boxes',
|
|
191
|
+
'sys.images',
|
|
192
|
+
]);
|
|
193
|
+
export function isSavedViewSelfValueRef(value) {
|
|
194
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
195
|
+
return false;
|
|
196
|
+
const record = value;
|
|
197
|
+
return record.kind === 'self' && isSavedViewSelfSourceFieldRef(record.field);
|
|
198
|
+
}
|
|
199
|
+
export function canSavedViewFilterConditionUseSelfValue(field, operator) {
|
|
200
|
+
if (SAVED_VIEW_VALUELESS_OPERATORS.has(operator) || operator === 'withinLastDays') {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
if (isSavedViewDisplayOnlyField(field) || SELF_VALUE_BLOCKED_CONDITION_FIELDS.has(field)) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
export function canSavedViewSelfSourceField(field) {
|
|
209
|
+
if (field === SAVED_VIEW_SELF_ID_FIELD)
|
|
210
|
+
return true;
|
|
211
|
+
if (!isSavedViewFieldRef(field) || isSavedViewDisplayOnlyField(field))
|
|
212
|
+
return false;
|
|
213
|
+
return field !== 'sys.primary_resource_type' && field !== 'sys.images';
|
|
214
|
+
}
|
|
215
|
+
const savedViewSelfValueRefSchema = z.object({
|
|
216
|
+
kind: z.literal('self'),
|
|
217
|
+
field: z.custom(isSavedViewSelfSourceFieldRef, 'Invalid saved view self source field reference'),
|
|
218
|
+
});
|
|
219
|
+
export const savedViewFilterConditionSchema = z
|
|
220
|
+
.object({
|
|
221
|
+
kind: z.literal('condition'),
|
|
222
|
+
field: savedViewFieldRefSchema,
|
|
223
|
+
operator: z.enum(SAVED_VIEW_FILTER_OPERATORS),
|
|
224
|
+
value: savedViewFilterValueSchema.optional(),
|
|
225
|
+
valueRef: savedViewSelfValueRefSchema.optional(),
|
|
226
|
+
})
|
|
227
|
+
.superRefine((condition, context) => {
|
|
228
|
+
if (condition.field === 'sys.primary_resource_type') {
|
|
229
|
+
const comparesValue = condition.operator === 'equals' || condition.operator === 'notEquals';
|
|
230
|
+
const allowedPresence = condition.operator === 'isEmpty' || condition.operator === 'isNotEmpty';
|
|
231
|
+
if ((!comparesValue && !allowedPresence) ||
|
|
232
|
+
(comparesValue &&
|
|
233
|
+
!SAVED_VIEW_PRIMARY_RESOURCE_TYPES.some((option) => option === condition.value))) {
|
|
234
|
+
context.addIssue({
|
|
235
|
+
code: z.ZodIssueCode.custom,
|
|
236
|
+
path: ['value'],
|
|
237
|
+
message: 'Invalid primary resource type filter',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const hasSelfValue = isSavedViewSelfValueRef(condition.valueRef);
|
|
242
|
+
const hasLiteralValue = condition.value !== undefined && condition.value !== null;
|
|
243
|
+
if (hasSelfValue && hasLiteralValue) {
|
|
244
|
+
context.addIssue({
|
|
245
|
+
code: z.ZodIssueCode.custom,
|
|
246
|
+
path: ['valueRef'],
|
|
247
|
+
message: 'Saved view condition cannot combine a literal value with a self value ref',
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (hasSelfValue) {
|
|
251
|
+
if (!canSavedViewFilterConditionUseSelfValue(condition.field, condition.operator)) {
|
|
252
|
+
context.addIssue({
|
|
253
|
+
code: z.ZodIssueCode.custom,
|
|
254
|
+
path: ['valueRef'],
|
|
255
|
+
message: `Saved view operator ${condition.operator} does not accept a self value`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (condition.valueRef && !canSavedViewSelfSourceField(condition.valueRef.field)) {
|
|
259
|
+
context.addIssue({
|
|
260
|
+
code: z.ZodIssueCode.custom,
|
|
261
|
+
path: ['valueRef', 'field'],
|
|
262
|
+
message: `${condition.valueRef.field} cannot be used as a self filter value`,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (SAVED_VIEW_VALUE_REQUIRED_OPERATORS.has(condition.operator) &&
|
|
267
|
+
!hasSelfValue &&
|
|
268
|
+
!hasLiteralValue) {
|
|
269
|
+
context.addIssue({
|
|
270
|
+
code: z.ZodIssueCode.custom,
|
|
271
|
+
path: ['value'],
|
|
272
|
+
message: `Saved view operator ${condition.operator} requires a value`,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (SAVED_VIEW_VALUE_REQUIRED_OPERATORS.has(condition.operator) &&
|
|
276
|
+
!hasSelfValue &&
|
|
277
|
+
typeof condition.value === 'string' &&
|
|
278
|
+
condition.value.trim().length === 0) {
|
|
279
|
+
context.addIssue({
|
|
280
|
+
code: z.ZodIssueCode.custom,
|
|
281
|
+
path: ['value'],
|
|
282
|
+
message: `Saved view operator ${condition.operator} requires a non-empty value`,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (SAVED_VIEW_VALUELESS_OPERATORS.has(condition.operator) &&
|
|
286
|
+
(hasLiteralValue || hasSelfValue)) {
|
|
287
|
+
context.addIssue({
|
|
288
|
+
code: z.ZodIssueCode.custom,
|
|
289
|
+
path: hasSelfValue ? ['valueRef'] : ['value'],
|
|
290
|
+
message: `Saved view operator ${condition.operator} does not accept a value`,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (condition.operator === 'withinLastDays' &&
|
|
294
|
+
(typeof condition.value !== 'number' ||
|
|
295
|
+
!Number.isInteger(condition.value) ||
|
|
296
|
+
condition.value < 1 ||
|
|
297
|
+
condition.value > MAX_SAVED_VIEW_RELATIVE_DAYS)) {
|
|
298
|
+
context.addIssue({
|
|
299
|
+
code: z.ZodIssueCode.custom,
|
|
300
|
+
path: ['value'],
|
|
301
|
+
message: `Saved view operator withinLastDays requires an integer from 1 to ${MAX_SAVED_VIEW_RELATIVE_DAYS}`,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
function isRawFilterNodeWithinLimits(value) {
|
|
306
|
+
const stack = [{ value, depth: 1 }];
|
|
307
|
+
const seen = new Set();
|
|
308
|
+
let count = 0;
|
|
309
|
+
while (stack.length > 0) {
|
|
310
|
+
const current = stack.pop();
|
|
311
|
+
if (!current)
|
|
312
|
+
continue;
|
|
313
|
+
if (!current.value ||
|
|
314
|
+
typeof current.value !== 'object' ||
|
|
315
|
+
Array.isArray(current.value)) {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (seen.has(current.value))
|
|
319
|
+
return false;
|
|
320
|
+
seen.add(current.value);
|
|
321
|
+
count += 1;
|
|
322
|
+
if (count > MAX_SAVED_VIEW_FILTER_NODES || current.depth > MAX_SAVED_VIEW_FILTER_DEPTH) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
const record = current.value;
|
|
326
|
+
if (record.kind === 'not') {
|
|
327
|
+
stack.push({ value: record.child, depth: current.depth + 1 });
|
|
328
|
+
}
|
|
329
|
+
else if (record.kind === 'group' && Array.isArray(record.children)) {
|
|
330
|
+
for (const child of record.children) {
|
|
331
|
+
stack.push({ value: child, depth: current.depth + 1 });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
const invalidSavedViewFilterNode = { kind: '__invalid_saved_view_filter__' };
|
|
338
|
+
export const savedViewFilterNodeSchema = z.preprocess((value) => (isRawFilterNodeWithinLimits(value) ? value : invalidSavedViewFilterNode), z.lazy(() => z.union([
|
|
339
|
+
savedViewFilterConditionSchema,
|
|
340
|
+
z.object({
|
|
341
|
+
kind: z.literal('group'),
|
|
342
|
+
operator: z.enum(['and', 'or']),
|
|
343
|
+
children: z.array(savedViewFilterNodeSchema).max(MAX_SAVED_VIEW_FILTER_NODES),
|
|
344
|
+
}),
|
|
345
|
+
z.object({
|
|
346
|
+
kind: z.literal('not'),
|
|
347
|
+
child: savedViewFilterNodeSchema,
|
|
348
|
+
}),
|
|
349
|
+
])));
|
|
350
|
+
export const savedViewFilterGroupSchema = z.object({
|
|
351
|
+
kind: z.literal('group'),
|
|
352
|
+
operator: z.enum(['and', 'or']),
|
|
353
|
+
children: z.array(savedViewFilterNodeSchema).max(100),
|
|
354
|
+
});
|
|
355
|
+
function inspectSavedViewFilterNode(node, depth, state) {
|
|
356
|
+
state.count += 1;
|
|
357
|
+
state.maxDepth = Math.max(state.maxDepth, depth);
|
|
358
|
+
if (node.kind === 'group') {
|
|
359
|
+
for (const child of node.children) {
|
|
360
|
+
inspectSavedViewFilterNode(child, depth + 1, state);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
else if (node.kind === 'not') {
|
|
364
|
+
inspectSavedViewFilterNode(node.child, depth + 1, state);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
export function savedViewFilterNodeUsesDynamicDate(node) {
|
|
368
|
+
if (!node)
|
|
369
|
+
return false;
|
|
370
|
+
if (node.kind === 'condition') {
|
|
371
|
+
return isSavedViewDynamicDateFilterOperator(node.operator);
|
|
372
|
+
}
|
|
373
|
+
if (node.kind === 'not') {
|
|
374
|
+
return savedViewFilterNodeUsesDynamicDate(node.child);
|
|
375
|
+
}
|
|
376
|
+
return node.children.some(savedViewFilterNodeUsesDynamicDate);
|
|
377
|
+
}
|
|
378
|
+
export function savedViewFilterUsesDynamicDate(filter) {
|
|
379
|
+
return savedViewFilterNodeUsesDynamicDate(filter.root);
|
|
380
|
+
}
|
|
381
|
+
export function savedViewFilterNodeUsesSelf(node) {
|
|
382
|
+
if (!node)
|
|
383
|
+
return false;
|
|
384
|
+
if (node.kind === 'condition')
|
|
385
|
+
return isSavedViewSelfValueRef(node.valueRef);
|
|
386
|
+
if (node.kind === 'not')
|
|
387
|
+
return savedViewFilterNodeUsesSelf(node.child);
|
|
388
|
+
return node.children.some(savedViewFilterNodeUsesSelf);
|
|
389
|
+
}
|
|
390
|
+
export function savedViewFilterUsesSelf(filter) {
|
|
391
|
+
return Boolean(filter && savedViewFilterNodeUsesSelf(filter.root));
|
|
392
|
+
}
|
|
393
|
+
export function createSavedViewFilter(root) {
|
|
394
|
+
if (savedViewFilterNodeUsesSelf(root))
|
|
395
|
+
return { version: 3, root };
|
|
396
|
+
return {
|
|
397
|
+
version: savedViewFilterNodeUsesDynamicDate(root) ? 2 : 1,
|
|
398
|
+
root,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
export function resolveStandaloneSavedViewFilter(config, filter) {
|
|
402
|
+
const rule = config.noteDetail;
|
|
403
|
+
if (!rule || !savedViewFilterUsesSelf(filter))
|
|
404
|
+
return filter;
|
|
405
|
+
return {
|
|
406
|
+
version: 3,
|
|
407
|
+
root: {
|
|
408
|
+
kind: 'condition',
|
|
409
|
+
field: rule.field,
|
|
410
|
+
operator: 'equals',
|
|
411
|
+
value: rule.value,
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
const SAVED_VIEW_SELF_HOST_FIELDS = {
|
|
416
|
+
title: (host) => host.title,
|
|
417
|
+
source: (host) => host.source,
|
|
418
|
+
type: (host) => host.type,
|
|
419
|
+
created_at: (host) => host.createdAt,
|
|
420
|
+
updated_at: (host) => host.updatedAt,
|
|
421
|
+
starred_at: (host) => host.starredAt,
|
|
422
|
+
tags: (host) => host.tags,
|
|
423
|
+
zettel_boxes: (host) => host.zettelBoxes,
|
|
424
|
+
};
|
|
425
|
+
function asScalarSavedViewFilterValue(value) {
|
|
426
|
+
if (typeof value === 'boolean')
|
|
427
|
+
return value;
|
|
428
|
+
if (typeof value === 'number')
|
|
429
|
+
return Number.isFinite(value) ? value : undefined;
|
|
430
|
+
if (typeof value === 'string') {
|
|
431
|
+
const trimmed = value.trim();
|
|
432
|
+
return trimmed ? trimmed : undefined;
|
|
433
|
+
}
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
function asSavedViewFilterValue(value) {
|
|
437
|
+
if (Array.isArray(value)) {
|
|
438
|
+
if (value.length !== 1)
|
|
439
|
+
return undefined;
|
|
440
|
+
return asScalarSavedViewFilterValue(value[0]);
|
|
441
|
+
}
|
|
442
|
+
return asScalarSavedViewFilterValue(value);
|
|
443
|
+
}
|
|
444
|
+
export function createSavedViewSelfBindContext(host) {
|
|
445
|
+
return {
|
|
446
|
+
resolve(field) {
|
|
447
|
+
if (!canSavedViewSelfSourceField(field))
|
|
448
|
+
return undefined;
|
|
449
|
+
if (field === SAVED_VIEW_SELF_ID_FIELD)
|
|
450
|
+
return asScalarSavedViewFilterValue(host.id);
|
|
451
|
+
if (field.startsWith('sys.')) {
|
|
452
|
+
const raw = SAVED_VIEW_SELF_HOST_FIELDS[field.slice('sys.'.length)]?.(host);
|
|
453
|
+
return asSavedViewFilterValue(raw);
|
|
454
|
+
}
|
|
455
|
+
const key = field.slice('prop.'.length);
|
|
456
|
+
return asSavedViewFilterValue(host.properties?.[key]);
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function bindSavedViewCondition(condition, context) {
|
|
461
|
+
if (!isSavedViewSelfValueRef(condition.valueRef))
|
|
462
|
+
return condition;
|
|
463
|
+
const resolved = context?.resolve(condition.valueRef.field);
|
|
464
|
+
if (resolved === undefined)
|
|
465
|
+
return condition;
|
|
466
|
+
return {
|
|
467
|
+
kind: 'condition',
|
|
468
|
+
field: condition.field,
|
|
469
|
+
operator: condition.operator,
|
|
470
|
+
value: resolved,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function bindSavedViewNode(node, context) {
|
|
474
|
+
if (node.kind === 'condition')
|
|
475
|
+
return bindSavedViewCondition(node, context);
|
|
476
|
+
if (node.kind === 'not') {
|
|
477
|
+
const child = bindSavedViewNode(node.child, context);
|
|
478
|
+
return child === node.child ? node : { kind: 'not', child };
|
|
479
|
+
}
|
|
480
|
+
const children = node.children.map((child) => bindSavedViewNode(child, context));
|
|
481
|
+
const unchanged = children.every((child, index) => child === node.children[index]);
|
|
482
|
+
return unchanged ? node : { kind: 'group', operator: node.operator, children };
|
|
483
|
+
}
|
|
484
|
+
/** Replace resolved `$self` refs with literals. Unresolved refs stay and fail closed in SQL. */
|
|
485
|
+
export function bindSavedViewFilter(filter, context) {
|
|
486
|
+
if (!filter)
|
|
487
|
+
return null;
|
|
488
|
+
if (!filter.root)
|
|
489
|
+
return filter;
|
|
490
|
+
const root = bindSavedViewNode(filter.root, context ?? null);
|
|
491
|
+
return root === filter.root ? filter : { ...filter, root };
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* A detail section appears only when `noteDetail` matches this note.
|
|
495
|
+
* The returned filter is the view filter with `$self` bound to the note.
|
|
496
|
+
*/
|
|
497
|
+
export function bindDetailSavedView(view, host) {
|
|
498
|
+
const rule = view.config.noteDetail;
|
|
499
|
+
if (!rule || !savedViewFilterUsesSelf(view.filter))
|
|
500
|
+
return null;
|
|
501
|
+
const context = createSavedViewSelfBindContext(host);
|
|
502
|
+
if (context.resolve(rule.field) !== rule.value)
|
|
503
|
+
return null;
|
|
504
|
+
return bindSavedViewFilter(view.filter, context);
|
|
505
|
+
}
|
|
506
|
+
export const savedViewFilterSchema = z
|
|
507
|
+
.object({
|
|
508
|
+
version: z.union([z.literal(1), z.literal(2), z.literal(3)]),
|
|
509
|
+
root: savedViewFilterNodeSchema.nullable(),
|
|
510
|
+
})
|
|
511
|
+
.superRefine((filter, context) => {
|
|
512
|
+
const usesSelf = savedViewFilterNodeUsesSelf(filter.root);
|
|
513
|
+
const usesDynamicDate = savedViewFilterNodeUsesDynamicDate(filter.root);
|
|
514
|
+
if (filter.version === 1 && (usesDynamicDate || usesSelf)) {
|
|
515
|
+
context.addIssue({
|
|
516
|
+
code: z.ZodIssueCode.custom,
|
|
517
|
+
path: ['version'],
|
|
518
|
+
message: usesSelf
|
|
519
|
+
? 'Self filter values require saved view filter version 3'
|
|
520
|
+
: 'Dynamic date filters require saved view filter version 2',
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
if (filter.version === 2 && usesSelf) {
|
|
524
|
+
context.addIssue({
|
|
525
|
+
code: z.ZodIssueCode.custom,
|
|
526
|
+
path: ['version'],
|
|
527
|
+
message: 'Self filter values require saved view filter version 3',
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
if (!filter.root)
|
|
531
|
+
return;
|
|
532
|
+
const state = { count: 0, maxDepth: 0 };
|
|
533
|
+
inspectSavedViewFilterNode(filter.root, 1, state);
|
|
534
|
+
if (state.count > MAX_SAVED_VIEW_FILTER_NODES) {
|
|
535
|
+
context.addIssue({
|
|
536
|
+
code: z.ZodIssueCode.custom,
|
|
537
|
+
path: ['root'],
|
|
538
|
+
message: `Saved view filters support at most ${MAX_SAVED_VIEW_FILTER_NODES} nodes`,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
if (state.maxDepth > MAX_SAVED_VIEW_FILTER_DEPTH) {
|
|
542
|
+
context.addIssue({
|
|
543
|
+
code: z.ZodIssueCode.custom,
|
|
544
|
+
path: ['root'],
|
|
545
|
+
message: `Saved view filters support at most ${MAX_SAVED_VIEW_FILTER_DEPTH} levels`,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
export const SAVED_VIEW_LAYOUTS = ['table', 'list', 'card'];
|
|
550
|
+
/**
|
|
551
|
+
* Analytics layer on `config_json` (shared contract with dinox-app).
|
|
552
|
+
* Both clients author and render these keys; every config write must round-trip them.
|
|
553
|
+
*/
|
|
554
|
+
export const SAVED_VIEW_CHART_TYPES = [
|
|
555
|
+
'bar',
|
|
556
|
+
'line',
|
|
557
|
+
'pie',
|
|
558
|
+
'area',
|
|
559
|
+
'contribute',
|
|
560
|
+
'waterfall',
|
|
561
|
+
'ring',
|
|
562
|
+
'funnel',
|
|
563
|
+
'scatter',
|
|
564
|
+
'treemap',
|
|
565
|
+
];
|
|
566
|
+
export const SAVED_VIEW_CHART_AGGREGATIONS = ['count', 'sum', 'avg'];
|
|
567
|
+
export const SAVED_VIEW_CHART_TIME_BUCKETS = ['hour', 'day', 'week', 'month'];
|
|
568
|
+
export const SAVED_VIEW_CHART_EXPR_OPS = ['*', '/', '+', '-'];
|
|
569
|
+
export const MAX_SAVED_VIEW_CHARTS = 5;
|
|
570
|
+
export const MAX_SAVED_VIEW_STATS = 6;
|
|
571
|
+
export const MAX_SAVED_VIEW_CHART_TITLE_LENGTH = 80;
|
|
572
|
+
export const MAX_SAVED_VIEW_STAT_TITLE_LENGTH = 80;
|
|
573
|
+
export const MAX_SAVED_VIEW_STAT_UNIT_LENGTH = 16;
|
|
574
|
+
export const MAX_SAVED_VIEW_CHART_LIMIT = 500;
|
|
575
|
+
export const MAX_SAVED_VIEW_COMPUTED_COLUMNS = 10;
|
|
576
|
+
export const MAX_SAVED_VIEW_SUMMARY_ITEMS = 20;
|
|
577
|
+
export const MAX_SAVED_VIEW_COMPUTED_COLUMN_TITLE_LENGTH = 80;
|
|
578
|
+
export const savedViewColumnSchema = z.object({
|
|
579
|
+
field: savedViewFieldRefSchema,
|
|
580
|
+
});
|
|
581
|
+
export const savedViewSortSchema = z.object({
|
|
582
|
+
field: savedViewFieldRefSchema,
|
|
583
|
+
direction: z.enum(['asc', 'desc']),
|
|
584
|
+
});
|
|
585
|
+
const SYS_DATE_FIELD_REFS = new Set([
|
|
586
|
+
'sys.created_at',
|
|
587
|
+
'sys.updated_at',
|
|
588
|
+
'sys.starred_at',
|
|
589
|
+
]);
|
|
590
|
+
const SYS_MULTI_VALUE_FIELD_REFS = new Set([
|
|
591
|
+
'sys.tags',
|
|
592
|
+
'sys.zettel_boxes',
|
|
593
|
+
]);
|
|
594
|
+
export function isSavedViewDateField(field, type) {
|
|
595
|
+
if (field.startsWith('sys.'))
|
|
596
|
+
return SYS_DATE_FIELD_REFS.has(field);
|
|
597
|
+
return type === 'date';
|
|
598
|
+
}
|
|
599
|
+
export function isSavedViewMultiValueField(field, type) {
|
|
600
|
+
if (field.startsWith('sys.'))
|
|
601
|
+
return SYS_MULTI_VALUE_FIELD_REFS.has(field);
|
|
602
|
+
return type === 'multi_select' || type === 'relation';
|
|
603
|
+
}
|
|
604
|
+
export function canGroupSavedViewChartByField(field, type) {
|
|
605
|
+
if (field === 'sys.primary_resource_type' ||
|
|
606
|
+
field === 'sys.title' ||
|
|
607
|
+
field === 'sys.images' ||
|
|
608
|
+
isSavedViewDisplayOnlyField(field)) {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
if (field.startsWith('sys.'))
|
|
612
|
+
return true;
|
|
613
|
+
return (type !== undefined &&
|
|
614
|
+
type !== 'files' &&
|
|
615
|
+
type !== 'relation' &&
|
|
616
|
+
type !== 'place' &&
|
|
617
|
+
type !== 'unique_id');
|
|
618
|
+
}
|
|
619
|
+
export function getSavedViewChartTimeBucketsForField(field, type) {
|
|
620
|
+
return isSavedViewDateField(field, type) ? [...SAVED_VIEW_CHART_TIME_BUCKETS] : [];
|
|
621
|
+
}
|
|
622
|
+
export function canUseSavedViewChartMeasureField(field, type) {
|
|
623
|
+
return field.startsWith('prop.') && type === 'number';
|
|
624
|
+
}
|
|
625
|
+
export function getSavedViewChartAggregationsForDimension(dimensionField, dimensionType) {
|
|
626
|
+
if (isSavedViewMultiValueField(dimensionField, dimensionType))
|
|
627
|
+
return ['count'];
|
|
628
|
+
return [...SAVED_VIEW_CHART_AGGREGATIONS];
|
|
629
|
+
}
|
|
630
|
+
const savedViewChartDimensionSchema = z
|
|
631
|
+
.object({
|
|
632
|
+
field: savedViewFieldRefSchema,
|
|
633
|
+
timeBucket: z.enum(SAVED_VIEW_CHART_TIME_BUCKETS).optional(),
|
|
634
|
+
})
|
|
635
|
+
.superRefine((dimension, context) => {
|
|
636
|
+
if (dimension.field === 'sys.primary_resource_type' ||
|
|
637
|
+
dimension.field === 'sys.title' ||
|
|
638
|
+
dimension.field === 'sys.images' ||
|
|
639
|
+
isSavedViewDisplayOnlyField(dimension.field)) {
|
|
640
|
+
context.addIssue({
|
|
641
|
+
code: z.ZodIssueCode.custom,
|
|
642
|
+
path: ['field'],
|
|
643
|
+
message: `${dimension.field} cannot be used as a chart dimension`,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
if (dimension.field.startsWith('sys.')) {
|
|
647
|
+
const isDate = SYS_DATE_FIELD_REFS.has(dimension.field);
|
|
648
|
+
if (isDate && !dimension.timeBucket) {
|
|
649
|
+
context.addIssue({
|
|
650
|
+
code: z.ZodIssueCode.custom,
|
|
651
|
+
path: ['timeBucket'],
|
|
652
|
+
message: 'Date chart dimensions require a timeBucket',
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
if (!isDate && dimension.timeBucket) {
|
|
656
|
+
context.addIssue({
|
|
657
|
+
code: z.ZodIssueCode.custom,
|
|
658
|
+
path: ['timeBucket'],
|
|
659
|
+
message: 'timeBucket is only allowed on date chart dimensions',
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
const savedViewChartBinaryExprSchema = z
|
|
665
|
+
.object({
|
|
666
|
+
op: z.enum(SAVED_VIEW_CHART_EXPR_OPS),
|
|
667
|
+
left: savedViewFieldRefSchema,
|
|
668
|
+
right: savedViewFieldRefSchema,
|
|
669
|
+
})
|
|
670
|
+
.superRefine((expr, context) => {
|
|
671
|
+
if (!expr.left.startsWith('prop.')) {
|
|
672
|
+
context.addIssue({
|
|
673
|
+
code: z.ZodIssueCode.custom,
|
|
674
|
+
path: ['left'],
|
|
675
|
+
message: 'Chart expressions only support number property fields on the left',
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
if (!expr.right.startsWith('prop.')) {
|
|
679
|
+
context.addIssue({
|
|
680
|
+
code: z.ZodIssueCode.custom,
|
|
681
|
+
path: ['right'],
|
|
682
|
+
message: 'Chart expressions only support number property fields on the right',
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
const savedViewChartMeasureSchema = z
|
|
687
|
+
.object({
|
|
688
|
+
field: savedViewFieldRefSchema.nullable(),
|
|
689
|
+
aggregation: z.enum(SAVED_VIEW_CHART_AGGREGATIONS),
|
|
690
|
+
expr: savedViewChartBinaryExprSchema.optional(),
|
|
691
|
+
})
|
|
692
|
+
.superRefine((measure, context) => {
|
|
693
|
+
if (measure.aggregation === 'count') {
|
|
694
|
+
if (measure.field !== null) {
|
|
695
|
+
context.addIssue({
|
|
696
|
+
code: z.ZodIssueCode.custom,
|
|
697
|
+
path: ['field'],
|
|
698
|
+
message: 'count does not accept a measure field',
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
if (measure.expr) {
|
|
702
|
+
context.addIssue({
|
|
703
|
+
code: z.ZodIssueCode.custom,
|
|
704
|
+
path: ['expr'],
|
|
705
|
+
message: 'count does not accept a measure expression',
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const hasField = measure.field !== null;
|
|
711
|
+
const hasExpr = measure.expr !== undefined;
|
|
712
|
+
if (hasField === hasExpr) {
|
|
713
|
+
context.addIssue({
|
|
714
|
+
code: z.ZodIssueCode.custom,
|
|
715
|
+
path: hasExpr ? ['field'] : ['expr'],
|
|
716
|
+
message: `${measure.aggregation} requires either a measure field or an expression, not both`,
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
if (hasField && measure.field && !measure.field.startsWith('prop.')) {
|
|
720
|
+
context.addIssue({
|
|
721
|
+
code: z.ZodIssueCode.custom,
|
|
722
|
+
path: ['field'],
|
|
723
|
+
message: `${measure.aggregation} only supports number property fields`,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
export const savedViewChartSchema = z
|
|
728
|
+
.object({
|
|
729
|
+
id: z.string().trim().min(1).max(64),
|
|
730
|
+
title: z.string().max(MAX_SAVED_VIEW_CHART_TITLE_LENGTH).optional(),
|
|
731
|
+
type: z.enum(SAVED_VIEW_CHART_TYPES),
|
|
732
|
+
dimensions: z.tuple([savedViewChartDimensionSchema]),
|
|
733
|
+
measures: z.tuple([savedViewChartMeasureSchema]),
|
|
734
|
+
orderBy: z
|
|
735
|
+
.object({
|
|
736
|
+
by: z.enum(['bucket', 'value']),
|
|
737
|
+
direction: z.enum(['asc', 'desc']),
|
|
738
|
+
})
|
|
739
|
+
.optional(),
|
|
740
|
+
limit: z.number().int().min(1).max(MAX_SAVED_VIEW_CHART_LIMIT).optional(),
|
|
741
|
+
})
|
|
742
|
+
.superRefine((chart, context) => {
|
|
743
|
+
if (chart.type === 'contribute' && chart.dimensions[0]?.timeBucket !== 'day') {
|
|
744
|
+
context.addIssue({
|
|
745
|
+
code: z.ZodIssueCode.custom,
|
|
746
|
+
path: ['dimensions', 0, 'timeBucket'],
|
|
747
|
+
message: 'contribute charts require a day timeBucket dimension',
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
export const savedViewStatSchema = z.object({
|
|
752
|
+
id: z.string().trim().min(1).max(64),
|
|
753
|
+
title: z.string().max(MAX_SAVED_VIEW_STAT_TITLE_LENGTH).optional(),
|
|
754
|
+
unit: z.string().trim().max(MAX_SAVED_VIEW_STAT_UNIT_LENGTH).optional(),
|
|
755
|
+
measure: savedViewChartMeasureSchema,
|
|
756
|
+
});
|
|
757
|
+
const CALC_COLUMN_KEY_PATTERN = /^calc\.[0-9A-Za-z_-]{1,64}$/;
|
|
758
|
+
export function isSavedViewTableColumnKey(value) {
|
|
759
|
+
if (isSavedViewFieldRef(value))
|
|
760
|
+
return true;
|
|
761
|
+
return typeof value === 'string' && CALC_COLUMN_KEY_PATTERN.test(value);
|
|
762
|
+
}
|
|
763
|
+
const savedViewTableColumnKeySchema = z.custom(isSavedViewTableColumnKey, 'Invalid saved view table column key');
|
|
764
|
+
export const savedViewComputedColumnSchema = z.object({
|
|
765
|
+
id: z.string().trim().min(1).max(64),
|
|
766
|
+
title: z.string().max(MAX_SAVED_VIEW_COMPUTED_COLUMN_TITLE_LENGTH).optional(),
|
|
767
|
+
expr: savedViewChartBinaryExprSchema,
|
|
768
|
+
});
|
|
769
|
+
export const savedViewSummaryItemSchema = z.object({
|
|
770
|
+
column: savedViewTableColumnKeySchema,
|
|
771
|
+
aggregation: z.enum(SAVED_VIEW_CHART_AGGREGATIONS),
|
|
772
|
+
});
|
|
773
|
+
export const savedViewConfigSchema = z
|
|
774
|
+
.object({
|
|
775
|
+
version: z.literal(1),
|
|
776
|
+
noteDetail: z
|
|
777
|
+
.object({
|
|
778
|
+
field: savedViewFieldRefSchema,
|
|
779
|
+
value: z.union([z.string().min(1), z.number().finite(), z.boolean()]),
|
|
780
|
+
})
|
|
781
|
+
.optional(),
|
|
782
|
+
layout: z.enum(SAVED_VIEW_LAYOUTS),
|
|
783
|
+
columns: z.array(savedViewColumnSchema).min(1).max(100),
|
|
784
|
+
sort: z.array(savedViewSortSchema).max(20).optional(),
|
|
785
|
+
stats: z.array(savedViewStatSchema).max(MAX_SAVED_VIEW_STATS).optional(),
|
|
786
|
+
charts: z.array(savedViewChartSchema).max(MAX_SAVED_VIEW_CHARTS).optional(),
|
|
787
|
+
computedColumns: z
|
|
788
|
+
.array(savedViewComputedColumnSchema)
|
|
789
|
+
.max(MAX_SAVED_VIEW_COMPUTED_COLUMNS)
|
|
790
|
+
.optional(),
|
|
791
|
+
summary: z.array(savedViewSummaryItemSchema).max(MAX_SAVED_VIEW_SUMMARY_ITEMS).optional(),
|
|
792
|
+
})
|
|
793
|
+
.superRefine((config, context) => {
|
|
794
|
+
if (config.columns[0]?.field !== 'sys.title') {
|
|
795
|
+
context.addIssue({
|
|
796
|
+
code: z.ZodIssueCode.custom,
|
|
797
|
+
path: ['columns', 0, 'field'],
|
|
798
|
+
message: 'The title field must be the first saved view column',
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
const columnFields = new Set();
|
|
802
|
+
config.columns.forEach((column, index) => {
|
|
803
|
+
if (columnFields.has(column.field)) {
|
|
804
|
+
context.addIssue({
|
|
805
|
+
code: z.ZodIssueCode.custom,
|
|
806
|
+
path: ['columns', index, 'field'],
|
|
807
|
+
message: 'Saved view columns must be unique',
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
columnFields.add(column.field);
|
|
811
|
+
});
|
|
812
|
+
const sortFields = new Set();
|
|
813
|
+
config.sort?.forEach((sort, index) => {
|
|
814
|
+
if (sortFields.has(sort.field)) {
|
|
815
|
+
context.addIssue({
|
|
816
|
+
code: z.ZodIssueCode.custom,
|
|
817
|
+
path: ['sort', index, 'field'],
|
|
818
|
+
message: 'Saved view sort fields must be unique',
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
sortFields.add(sort.field);
|
|
822
|
+
});
|
|
823
|
+
const statIds = new Set();
|
|
824
|
+
config.stats?.forEach((stat, index) => {
|
|
825
|
+
if (statIds.has(stat.id)) {
|
|
826
|
+
context.addIssue({
|
|
827
|
+
code: z.ZodIssueCode.custom,
|
|
828
|
+
path: ['stats', index, 'id'],
|
|
829
|
+
message: 'Saved view stat ids must be unique',
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
statIds.add(stat.id);
|
|
833
|
+
});
|
|
834
|
+
const chartIds = new Set();
|
|
835
|
+
config.charts?.forEach((chart, index) => {
|
|
836
|
+
if (chartIds.has(chart.id)) {
|
|
837
|
+
context.addIssue({
|
|
838
|
+
code: z.ZodIssueCode.custom,
|
|
839
|
+
path: ['charts', index, 'id'],
|
|
840
|
+
message: 'Saved view chart ids must be unique',
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
chartIds.add(chart.id);
|
|
844
|
+
});
|
|
845
|
+
const computedColumnIds = new Set();
|
|
846
|
+
config.computedColumns?.forEach((column, index) => {
|
|
847
|
+
if (computedColumnIds.has(column.id)) {
|
|
848
|
+
context.addIssue({
|
|
849
|
+
code: z.ZodIssueCode.custom,
|
|
850
|
+
path: ['computedColumns', index, 'id'],
|
|
851
|
+
message: 'Saved view computed column ids must be unique',
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
computedColumnIds.add(column.id);
|
|
855
|
+
});
|
|
856
|
+
const summaryColumns = new Set();
|
|
857
|
+
config.summary?.forEach((item, index) => {
|
|
858
|
+
if (summaryColumns.has(item.column)) {
|
|
859
|
+
context.addIssue({
|
|
860
|
+
code: z.ZodIssueCode.custom,
|
|
861
|
+
path: ['summary', index, 'column'],
|
|
862
|
+
message: 'Saved view summary columns must be unique',
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
summaryColumns.add(item.column);
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
export function createDefaultSavedViewFilter() {
|
|
869
|
+
return createSavedViewFilter(null);
|
|
870
|
+
}
|
|
871
|
+
export function createDefaultSavedViewConfig() {
|
|
872
|
+
return {
|
|
873
|
+
version: 1,
|
|
874
|
+
layout: 'table',
|
|
875
|
+
columns: [{ field: 'sys.title' }, { field: 'sys.updated_at' }],
|
|
876
|
+
sort: [{ field: 'sys.updated_at', direction: 'desc' }],
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
export const aiGenerateSavedViewPlanRequestSchema = z.object({
|
|
880
|
+
prompt: z.string().trim().min(1).max(4000),
|
|
881
|
+
modelSelector: z.string().trim().min(1).max(512).optional(),
|
|
882
|
+
locale: z.string().trim().min(1).max(35).optional(),
|
|
883
|
+
timeZone: z.string().trim().min(1).max(100).optional(),
|
|
884
|
+
}).strict();
|
|
885
|
+
const savedViewZettelBoxDetailsSchema = z
|
|
886
|
+
.array(z.object({
|
|
887
|
+
fullPath: z.string().trim().min(1).max(1_000),
|
|
888
|
+
color: z.string().trim().max(200).nullable(),
|
|
889
|
+
}))
|
|
890
|
+
.max(100);
|
|
891
|
+
export function parseSavedViewZettelBoxDetails(value) {
|
|
892
|
+
if (!value)
|
|
893
|
+
return [];
|
|
894
|
+
try {
|
|
895
|
+
const parsed = savedViewZettelBoxDetailsSchema.safeParse(JSON.parse(value));
|
|
896
|
+
return parsed.success ? parsed.data : [];
|
|
897
|
+
}
|
|
898
|
+
catch {
|
|
899
|
+
return [];
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const nullableShortStringSchema = z.string().trim().max(120).nullable();
|
|
903
|
+
const nullableIdSchema = z.string().trim().min(1).max(200).nullable();
|
|
904
|
+
const nullableTimestampSchema = z.string().trim().min(1).max(64).nullable();
|
|
905
|
+
export const savedViewCatalogInputSchema = z.object({
|
|
906
|
+
userId: z.string().trim().min(1),
|
|
907
|
+
limit: z.number().int().positive().max(5000).optional(),
|
|
908
|
+
});
|
|
909
|
+
export const savedViewCreateInputSchema = z.object({
|
|
910
|
+
userId: z.string().trim().min(1),
|
|
911
|
+
name: z.string().trim().min(1).max(120),
|
|
912
|
+
emoji: nullableShortStringSchema.optional(),
|
|
913
|
+
filter: savedViewFilterSchema,
|
|
914
|
+
config: savedViewConfigSchema,
|
|
915
|
+
templateId: nullableIdSchema.optional(),
|
|
916
|
+
groupId: nullableIdSchema.optional(),
|
|
917
|
+
sortRank: z.number().int().nullable().optional(),
|
|
918
|
+
pinnedAt: nullableTimestampSchema.optional(),
|
|
919
|
+
updatedAt: z.string().trim().min(1).max(64),
|
|
920
|
+
});
|
|
921
|
+
export const savedViewUpdateInputSchema = z
|
|
922
|
+
.object({
|
|
923
|
+
id: z.string().trim().min(1),
|
|
924
|
+
userId: z.string().trim().min(1),
|
|
925
|
+
name: z.string().trim().min(1).max(120).optional(),
|
|
926
|
+
emoji: nullableShortStringSchema.optional(),
|
|
927
|
+
filter: savedViewFilterSchema.optional(),
|
|
928
|
+
config: savedViewConfigSchema.optional(),
|
|
929
|
+
templateId: nullableIdSchema.optional(),
|
|
930
|
+
groupId: nullableIdSchema.optional(),
|
|
931
|
+
sortRank: z.number().int().nullable().optional(),
|
|
932
|
+
pinnedAt: nullableTimestampSchema.optional(),
|
|
933
|
+
updatedAt: z.string().trim().min(1).max(64),
|
|
934
|
+
})
|
|
935
|
+
.refine((input) => input.name !== undefined ||
|
|
936
|
+
input.emoji !== undefined ||
|
|
937
|
+
input.filter !== undefined ||
|
|
938
|
+
input.config !== undefined ||
|
|
939
|
+
input.templateId !== undefined ||
|
|
940
|
+
input.groupId !== undefined ||
|
|
941
|
+
input.sortRank !== undefined ||
|
|
942
|
+
input.pinnedAt !== undefined, { message: 'At least one saved-view field must be updated' });
|
|
943
|
+
export const savedViewSoftDeleteInputSchema = z.object({
|
|
944
|
+
id: z.string().trim().min(1),
|
|
945
|
+
userId: z.string().trim().min(1),
|
|
946
|
+
updatedAt: z.string().trim().min(1).max(64),
|
|
947
|
+
});
|
|
948
|
+
export const savedViewPageInputSchema = z.object({
|
|
949
|
+
viewId: z.string().trim().min(1),
|
|
950
|
+
userId: z.string().trim().min(1),
|
|
951
|
+
timeZone: z.string().trim().min(1).max(100).optional(),
|
|
952
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
953
|
+
offset: z.number().int().nonnegative().optional(),
|
|
954
|
+
});
|
|
955
|
+
export const savedViewCountInputSchema = z.object({
|
|
956
|
+
viewId: z.string().trim().min(1),
|
|
957
|
+
userId: z.string().trim().min(1),
|
|
958
|
+
timeZone: z.string().trim().min(1).max(100).optional(),
|
|
959
|
+
});
|
|
960
|
+
export const savedViewAnalyticsInputSchema = z.object({
|
|
961
|
+
viewId: z.string().trim().min(1),
|
|
962
|
+
userId: z.string().trim().min(1),
|
|
963
|
+
timeZone: z.string().trim().min(1).max(100).optional(),
|
|
964
|
+
draft: z
|
|
965
|
+
.object({
|
|
966
|
+
stats: z.array(savedViewStatSchema).max(MAX_SAVED_VIEW_STATS).optional(),
|
|
967
|
+
charts: z.array(savedViewChartSchema).max(MAX_SAVED_VIEW_CHARTS).optional(),
|
|
968
|
+
computedColumns: z
|
|
969
|
+
.array(savedViewComputedColumnSchema)
|
|
970
|
+
.max(MAX_SAVED_VIEW_COMPUTED_COLUMNS)
|
|
971
|
+
.optional(),
|
|
972
|
+
summary: z.array(savedViewSummaryItemSchema).max(MAX_SAVED_VIEW_SUMMARY_ITEMS).optional(),
|
|
973
|
+
})
|
|
974
|
+
.optional(),
|
|
975
|
+
});
|
|
976
|
+
export const savedViewNoteDetailInputSchema = z.object({
|
|
977
|
+
userId: z.string().trim().min(1),
|
|
978
|
+
noteId: z.string().trim().min(1),
|
|
979
|
+
timeZone: z.string().trim().min(1).max(100).optional(),
|
|
980
|
+
limit: z.number().int().positive().max(100).optional(),
|
|
981
|
+
});
|