@dinoxx/dinox-cli 2.1.1 → 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 +72 -20
- package/dist/commands/notes/createFromTemplate.d.ts +35 -0
- package/dist/commands/notes/createFromTemplate.js +58 -0
- package/dist/commands/notes/descriptors.js +12 -5
- package/dist/commands/notes/dryRun.d.ts +1 -0
- package/dist/commands/notes/dryRun.js +8 -2
- package/dist/commands/notes/repo/read.js +4 -23
- package/dist/commands/notes/repo/shared.d.ts +2 -0
- package/dist/commands/notes/repo/write.d.ts +3 -0
- package/dist/commands/notes/repo/write.js +16 -5
- 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
|
@@ -1,70 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export declare const MAX_SAVED_VIEW_COLUMNS = 100;
|
|
7
|
-
export declare const MAX_SAVED_VIEW_SORTS = 20;
|
|
8
|
-
export declare const SAVED_VIEW_SYSTEM_FIELDS: readonly ["title", "created_at", "updated_at", "tags", "zettel_boxes", "source", "type", "starred_at"];
|
|
9
|
-
export declare const SAVED_VIEW_FILTER_OPERATORS: readonly ["equals", "notEquals", "contains", "notContains", "greaterThan", "greaterThanOrEqual", "lessThan", "lessThanOrEqual", "isEmpty", "isNotEmpty"];
|
|
10
|
-
export type SavedViewSystemField = (typeof SAVED_VIEW_SYSTEM_FIELDS)[number];
|
|
11
|
-
export type SavedViewFieldRef = `sys.${SavedViewSystemField}` | `prop.${string}`;
|
|
12
|
-
export type SavedViewFilterOperator = (typeof SAVED_VIEW_FILTER_OPERATORS)[number];
|
|
13
|
-
export type SavedViewFilterValue = string | number | boolean | null;
|
|
14
|
-
export type SavedViewFilterCondition = {
|
|
15
|
-
kind: 'condition';
|
|
16
|
-
field: SavedViewFieldRef;
|
|
17
|
-
operator: SavedViewFilterOperator;
|
|
18
|
-
value?: SavedViewFilterValue;
|
|
19
|
-
};
|
|
20
|
-
export type SavedViewFilterGroup = {
|
|
21
|
-
kind: 'group';
|
|
22
|
-
operator: 'and' | 'or';
|
|
23
|
-
children: SavedViewFilterNode[];
|
|
24
|
-
};
|
|
25
|
-
export type SavedViewFilterNot = {
|
|
26
|
-
kind: 'not';
|
|
27
|
-
child: SavedViewFilterNode;
|
|
28
|
-
};
|
|
29
|
-
export type SavedViewFilterNode = SavedViewFilterCondition | SavedViewFilterGroup | SavedViewFilterNot;
|
|
30
|
-
export type SavedViewFilter = {
|
|
31
|
-
version: typeof SAVED_VIEW_FILTER_VERSION;
|
|
32
|
-
root: SavedViewFilterNode | null;
|
|
33
|
-
};
|
|
34
|
-
export type SavedViewColumn = {
|
|
35
|
-
field: SavedViewFieldRef;
|
|
36
|
-
};
|
|
37
|
-
export type SavedViewSort = {
|
|
38
|
-
field: SavedViewFieldRef;
|
|
39
|
-
direction: 'asc' | 'desc';
|
|
40
|
-
};
|
|
41
|
-
export type SavedViewConfig = {
|
|
42
|
-
version: typeof SAVED_VIEW_CONFIG_VERSION;
|
|
43
|
-
layout: 'table';
|
|
44
|
-
columns: SavedViewColumn[];
|
|
45
|
-
sort?: SavedViewSort[];
|
|
46
|
-
};
|
|
47
|
-
export type SavedViewRow = {
|
|
48
|
-
id: string;
|
|
49
|
-
user_id: string | null;
|
|
50
|
-
name: string | null;
|
|
51
|
-
emoji: string | null;
|
|
52
|
-
filter_json: string | null;
|
|
53
|
-
view_type: string | null;
|
|
54
|
-
config_json: string | null;
|
|
55
|
-
template_id: string | null;
|
|
56
|
-
group_id: string | null;
|
|
57
|
-
sort_rank: number | null;
|
|
58
|
-
pinned_at: string | null;
|
|
59
|
-
created_at: string | null;
|
|
60
|
-
updated_at: string | null;
|
|
61
|
-
is_del: number | null;
|
|
62
|
-
};
|
|
1
|
+
import { type DinoxErrorCode } from '../../utils/errors.js';
|
|
2
|
+
import { createDefaultSavedViewConfig, createDefaultSavedViewFilter, SAVED_VIEW_LAYOUTS, type SavedViewCatalogRow, type SavedViewConfig, type SavedViewFilter, type SavedViewLayout } from '../../vendor/electron/shared/index.js';
|
|
3
|
+
export { createDefaultSavedViewConfig, createDefaultSavedViewFilter, SAVED_VIEW_LAYOUTS, type SavedViewConfig, type SavedViewFilter, type SavedViewLayout, };
|
|
4
|
+
export type { SavedViewFieldRef, SavedViewFilterCondition, SavedViewFilterNode, } from '../../vendor/electron/shared/index.js';
|
|
5
|
+
export type SavedViewRow = SavedViewCatalogRow;
|
|
63
6
|
export type SavedViewDefinition = {
|
|
64
7
|
id: string;
|
|
65
8
|
userId: string;
|
|
66
9
|
name: string;
|
|
67
10
|
emoji: string | null;
|
|
11
|
+
layout: SavedViewLayout;
|
|
68
12
|
filter: SavedViewFilter;
|
|
69
13
|
config: SavedViewConfig;
|
|
70
14
|
templateId: string | null;
|
|
@@ -74,14 +18,28 @@ export type SavedViewDefinition = {
|
|
|
74
18
|
createdAt: string | null;
|
|
75
19
|
updatedAt: string | null;
|
|
76
20
|
};
|
|
77
|
-
|
|
78
|
-
export
|
|
21
|
+
/** A stored row the CLI could not parse; returned instead of throwing so it stays inspectable. */
|
|
22
|
+
export type InvalidSavedViewDefinition = Omit<SavedViewDefinition, 'layout' | 'filter' | 'config'> & {
|
|
23
|
+
viewType: string | null;
|
|
24
|
+
filterJson: string | null;
|
|
25
|
+
configJson: string | null;
|
|
26
|
+
};
|
|
27
|
+
export type SavedViewInspection = {
|
|
28
|
+
valid: true;
|
|
29
|
+
view: SavedViewDefinition;
|
|
30
|
+
} | {
|
|
31
|
+
valid: false;
|
|
32
|
+
view: InvalidSavedViewDefinition;
|
|
33
|
+
error: {
|
|
34
|
+
code: DinoxErrorCode;
|
|
35
|
+
message: string;
|
|
36
|
+
issues?: unknown;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
79
39
|
export declare function validateSavedViewFilter(value: unknown): SavedViewFilter;
|
|
80
|
-
export declare function parseSavedViewFilter(raw: string | null | undefined): SavedViewFilter;
|
|
81
|
-
export declare function serializeSavedViewFilter(value: unknown): string;
|
|
82
40
|
export declare function validateSavedViewConfig(value: unknown): SavedViewConfig;
|
|
41
|
+
export declare function parseSavedViewFilter(raw: string | null | undefined): SavedViewFilter;
|
|
83
42
|
export declare function parseSavedViewConfig(raw: string | null | undefined): SavedViewConfig;
|
|
84
|
-
export declare function serializeSavedViewConfig(value: unknown): string;
|
|
85
|
-
export declare function createDefaultSavedViewFilter(): SavedViewFilter;
|
|
86
|
-
export declare function createDefaultSavedViewConfig(): SavedViewConfig;
|
|
87
43
|
export declare function mapSavedViewRow(row: SavedViewRow): SavedViewDefinition;
|
|
44
|
+
/** Parses a stored row without throwing, so catalogs and `view get` can surface bad rows. */
|
|
45
|
+
export declare function inspectSavedViewRow(row: SavedViewRow): SavedViewInspection;
|
|
@@ -1,301 +1,52 @@
|
|
|
1
|
+
// Saved-view definitions as Dinox desktop stores them. Validation is delegated to the vendored
|
|
2
|
+
// desktop zod schemas (src/vendor/electron/shared/saved-views.ts) so the CLI accepts every
|
|
3
|
+
// filter version, layout, operator, and analytics config the desktop writes.
|
|
1
4
|
import { DinoxError } from '../../utils/errors.js';
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const MAX_SAVED_VIEW_FILTER_VALUE_LENGTH = 2_000;
|
|
7
|
-
export const MAX_SAVED_VIEW_COLUMNS = 100;
|
|
8
|
-
export const MAX_SAVED_VIEW_SORTS = 20;
|
|
9
|
-
export const SAVED_VIEW_SYSTEM_FIELDS = [
|
|
10
|
-
'title',
|
|
11
|
-
'created_at',
|
|
12
|
-
'updated_at',
|
|
13
|
-
'tags',
|
|
14
|
-
'zettel_boxes',
|
|
15
|
-
'source',
|
|
16
|
-
'type',
|
|
17
|
-
'starred_at',
|
|
18
|
-
];
|
|
19
|
-
export const SAVED_VIEW_FILTER_OPERATORS = [
|
|
20
|
-
'equals',
|
|
21
|
-
'notEquals',
|
|
22
|
-
'contains',
|
|
23
|
-
'notContains',
|
|
24
|
-
'greaterThan',
|
|
25
|
-
'greaterThanOrEqual',
|
|
26
|
-
'lessThan',
|
|
27
|
-
'lessThanOrEqual',
|
|
28
|
-
'isEmpty',
|
|
29
|
-
'isNotEmpty',
|
|
30
|
-
];
|
|
31
|
-
const systemFieldRefs = new Set(SAVED_VIEW_SYSTEM_FIELDS.map((field) => `sys.${field}`));
|
|
32
|
-
const filterOperators = new Set(SAVED_VIEW_FILTER_OPERATORS);
|
|
33
|
-
const valueRequiredOperators = new Set([
|
|
34
|
-
'contains',
|
|
35
|
-
'notContains',
|
|
36
|
-
'greaterThan',
|
|
37
|
-
'greaterThanOrEqual',
|
|
38
|
-
'lessThan',
|
|
39
|
-
'lessThanOrEqual',
|
|
40
|
-
]);
|
|
41
|
-
const valuelessOperators = new Set(['isEmpty', 'isNotEmpty']);
|
|
42
|
-
function formatError(message, code, details) {
|
|
43
|
-
return new DinoxError(message, {
|
|
44
|
-
code,
|
|
45
|
-
details,
|
|
46
|
-
recoverable: false,
|
|
47
|
-
});
|
|
5
|
+
import { createDefaultSavedViewConfig, createDefaultSavedViewFilter, SAVED_VIEW_LAYOUTS, savedViewConfigSchema, savedViewFilterSchema, } from '../../vendor/electron/shared/index.js';
|
|
6
|
+
export { createDefaultSavedViewConfig, createDefaultSavedViewFilter, SAVED_VIEW_LAYOUTS, };
|
|
7
|
+
function issuesOf(error) {
|
|
8
|
+
return error.issues.map((issue) => ({ path: issue.path.map(String).join('.'), message: issue.message }));
|
|
48
9
|
}
|
|
49
|
-
function
|
|
50
|
-
return
|
|
10
|
+
function invalid(code, message, issues) {
|
|
11
|
+
return new DinoxError(message, { code, details: issues === undefined ? undefined : { issues }, recoverable: false });
|
|
51
12
|
}
|
|
52
|
-
function
|
|
53
|
-
const allowedKeys = new Set(allowed);
|
|
54
|
-
const unknownKeys = Object.keys(record).filter((key) => !allowedKeys.has(key));
|
|
55
|
-
if (unknownKeys.length > 0) {
|
|
56
|
-
throw formatError(`${label} contains unsupported fields`, code, { unknownKeys });
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function parseStoredJson(raw, label, code) {
|
|
13
|
+
function parseJson(raw, label, code) {
|
|
60
14
|
const value = raw?.trim() ?? '';
|
|
61
|
-
if (!value)
|
|
62
|
-
throw
|
|
63
|
-
}
|
|
15
|
+
if (!value)
|
|
16
|
+
throw invalid(code, `${label} is required`);
|
|
64
17
|
try {
|
|
65
18
|
return JSON.parse(value);
|
|
66
19
|
}
|
|
67
|
-
catch
|
|
68
|
-
throw
|
|
69
|
-
cause: cause instanceof Error ? cause.message : String(cause),
|
|
70
|
-
});
|
|
20
|
+
catch {
|
|
21
|
+
throw invalid(code, `${label} must be valid JSON`);
|
|
71
22
|
}
|
|
72
23
|
}
|
|
73
|
-
export function isNotePropertyKeyValid(value) {
|
|
74
|
-
if (typeof value !== 'string')
|
|
75
|
-
return false;
|
|
76
|
-
const trimmed = value.trim();
|
|
77
|
-
if (!trimmed || trimmed !== value || trimmed.length > 64)
|
|
78
|
-
return false;
|
|
79
|
-
if (trimmed === '__proto__')
|
|
80
|
-
return false;
|
|
81
|
-
return !trimmed.includes('.') && !/[\n\r\t]/.test(trimmed);
|
|
82
|
-
}
|
|
83
|
-
export function isSavedViewFieldRef(value) {
|
|
84
|
-
if (typeof value !== 'string')
|
|
85
|
-
return false;
|
|
86
|
-
if (systemFieldRefs.has(value))
|
|
87
|
-
return true;
|
|
88
|
-
return value.startsWith('prop.') && isNotePropertyKeyValid(value.slice('prop.'.length));
|
|
89
|
-
}
|
|
90
|
-
function validateFilterValue(value, code) {
|
|
91
|
-
if (value === null || typeof value === 'boolean')
|
|
92
|
-
return value;
|
|
93
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
94
|
-
return value;
|
|
95
|
-
if (typeof value === 'string' && value.length <= MAX_SAVED_VIEW_FILTER_VALUE_LENGTH)
|
|
96
|
-
return value;
|
|
97
|
-
throw formatError('Saved view filter contains an invalid value', code, {
|
|
98
|
-
maxStringLength: MAX_SAVED_VIEW_FILTER_VALUE_LENGTH,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
function validateFilterNode(value, state, depth) {
|
|
102
|
-
const code = 'SAVED_VIEW_INVALID_FILTER';
|
|
103
|
-
if (depth > MAX_SAVED_VIEW_FILTER_DEPTH) {
|
|
104
|
-
throw formatError(`Saved view filters support at most ${MAX_SAVED_VIEW_FILTER_DEPTH} levels`, code);
|
|
105
|
-
}
|
|
106
|
-
if (!isPlainRecord(value)) {
|
|
107
|
-
throw formatError('Saved view filter nodes must be objects', code);
|
|
108
|
-
}
|
|
109
|
-
state.count += 1;
|
|
110
|
-
if (state.count > MAX_SAVED_VIEW_FILTER_NODES) {
|
|
111
|
-
throw formatError(`Saved view filters support at most ${MAX_SAVED_VIEW_FILTER_NODES} nodes`, code);
|
|
112
|
-
}
|
|
113
|
-
if (value.kind === 'condition') {
|
|
114
|
-
assertAllowedKeys(value, ['kind', 'field', 'operator', 'value'], 'Saved view filter condition', code);
|
|
115
|
-
if (!isSavedViewFieldRef(value.field)) {
|
|
116
|
-
throw formatError('Saved view filter contains an invalid field reference', code, { field: value.field });
|
|
117
|
-
}
|
|
118
|
-
if (typeof value.operator !== 'string' || !filterOperators.has(value.operator)) {
|
|
119
|
-
throw formatError('Saved view filter contains an invalid operator', code, { operator: value.operator });
|
|
120
|
-
}
|
|
121
|
-
const operator = value.operator;
|
|
122
|
-
const hasValue = Object.hasOwn(value, 'value');
|
|
123
|
-
const parsedValue = hasValue ? validateFilterValue(value.value, code) : undefined;
|
|
124
|
-
if (valueRequiredOperators.has(operator)
|
|
125
|
-
&& (parsedValue === undefined || parsedValue === null || (typeof parsedValue === 'string' && !parsedValue.trim()))) {
|
|
126
|
-
throw formatError(`Saved view operator ${operator} requires a non-empty value`, code);
|
|
127
|
-
}
|
|
128
|
-
if (valuelessOperators.has(operator) && parsedValue !== undefined && parsedValue !== null) {
|
|
129
|
-
throw formatError(`Saved view operator ${operator} does not accept a value`, code);
|
|
130
|
-
}
|
|
131
|
-
return {
|
|
132
|
-
kind: 'condition',
|
|
133
|
-
field: value.field,
|
|
134
|
-
operator,
|
|
135
|
-
...(hasValue ? { value: parsedValue } : {}),
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
if (value.kind === 'group') {
|
|
139
|
-
assertAllowedKeys(value, ['kind', 'operator', 'children'], 'Saved view filter group', code);
|
|
140
|
-
if (value.operator !== 'and' && value.operator !== 'or') {
|
|
141
|
-
throw formatError('Saved view filter group operator must be and or or', code);
|
|
142
|
-
}
|
|
143
|
-
if (!Array.isArray(value.children) || value.children.length > MAX_SAVED_VIEW_FILTER_NODES) {
|
|
144
|
-
throw formatError(`Saved view filter groups support at most ${MAX_SAVED_VIEW_FILTER_NODES} children`, code);
|
|
145
|
-
}
|
|
146
|
-
return {
|
|
147
|
-
kind: 'group',
|
|
148
|
-
operator: value.operator,
|
|
149
|
-
children: value.children.map((child) => validateFilterNode(child, state, depth + 1)),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
if (value.kind === 'not') {
|
|
153
|
-
assertAllowedKeys(value, ['kind', 'child'], 'Saved view filter not node', code);
|
|
154
|
-
return {
|
|
155
|
-
kind: 'not',
|
|
156
|
-
child: validateFilterNode(value.child, state, depth + 1),
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
throw formatError('Saved view filter contains an unsupported node kind', code, { kind: value.kind });
|
|
160
|
-
}
|
|
161
24
|
export function validateSavedViewFilter(value) {
|
|
162
|
-
const
|
|
163
|
-
if (!
|
|
164
|
-
throw
|
|
25
|
+
const parsed = savedViewFilterSchema.safeParse(value);
|
|
26
|
+
if (!parsed.success) {
|
|
27
|
+
throw invalid('SAVED_VIEW_INVALID_FILTER', 'Saved view filter is invalid', issuesOf(parsed.error));
|
|
165
28
|
}
|
|
166
|
-
|
|
167
|
-
throw formatError(`Unsupported saved view filter version: ${String(value.version)}`, 'SAVED_VIEW_UNSUPPORTED_FILTER_VERSION', {
|
|
168
|
-
supportedVersion: SAVED_VIEW_FILTER_VERSION,
|
|
169
|
-
receivedVersion: value.version,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
assertAllowedKeys(value, ['version', 'root'], 'Saved view filter', code);
|
|
173
|
-
if (!Object.hasOwn(value, 'root')) {
|
|
174
|
-
throw formatError('Saved view filter root is required', code);
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
version: SAVED_VIEW_FILTER_VERSION,
|
|
178
|
-
root: value.root === null ? null : validateFilterNode(value.root, { count: 0 }, 1),
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
export function parseSavedViewFilter(raw) {
|
|
182
|
-
return validateSavedViewFilter(parseStoredJson(raw, 'Saved view filter_json', 'SAVED_VIEW_INVALID_FILTER'));
|
|
183
|
-
}
|
|
184
|
-
export function serializeSavedViewFilter(value) {
|
|
185
|
-
return JSON.stringify(validateSavedViewFilter(value));
|
|
29
|
+
return parsed.data;
|
|
186
30
|
}
|
|
187
31
|
export function validateSavedViewConfig(value) {
|
|
188
|
-
const
|
|
189
|
-
if (!
|
|
190
|
-
throw
|
|
191
|
-
}
|
|
192
|
-
if (value.version !== SAVED_VIEW_CONFIG_VERSION) {
|
|
193
|
-
throw formatError(`Unsupported saved view config version: ${String(value.version)}`, 'SAVED_VIEW_UNSUPPORTED_CONFIG_VERSION', {
|
|
194
|
-
supportedVersion: SAVED_VIEW_CONFIG_VERSION,
|
|
195
|
-
receivedVersion: value.version,
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
assertAllowedKeys(value, ['version', 'layout', 'columns', 'sort'], 'Saved view config', code);
|
|
199
|
-
if (value.layout !== 'table') {
|
|
200
|
-
throw formatError(`Unsupported saved view layout: ${String(value.layout)}`, 'SAVED_VIEW_UNSUPPORTED_LAYOUT', {
|
|
201
|
-
supportedLayouts: ['table'],
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
if (!Array.isArray(value.columns) || value.columns.length === 0 || value.columns.length > MAX_SAVED_VIEW_COLUMNS) {
|
|
205
|
-
throw formatError(`Saved view config requires 1-${MAX_SAVED_VIEW_COLUMNS} columns`, code);
|
|
32
|
+
const parsed = savedViewConfigSchema.safeParse(value);
|
|
33
|
+
if (!parsed.success) {
|
|
34
|
+
throw invalid('SAVED_VIEW_INVALID_CONFIG', 'Saved view config is invalid', issuesOf(parsed.error));
|
|
206
35
|
}
|
|
207
|
-
|
|
208
|
-
const columns = value.columns.map((column, index) => {
|
|
209
|
-
if (!isPlainRecord(column)) {
|
|
210
|
-
throw formatError('Saved view columns must be objects', code, { index });
|
|
211
|
-
}
|
|
212
|
-
assertAllowedKeys(column, ['field'], 'Saved view column', code);
|
|
213
|
-
if (!isSavedViewFieldRef(column.field)) {
|
|
214
|
-
throw formatError('Saved view config contains an invalid column field', code, { index, field: column.field });
|
|
215
|
-
}
|
|
216
|
-
if (columnFields.has(column.field)) {
|
|
217
|
-
throw formatError('Saved view columns must be unique', code, { index, field: column.field });
|
|
218
|
-
}
|
|
219
|
-
columnFields.add(column.field);
|
|
220
|
-
return { field: column.field };
|
|
221
|
-
});
|
|
222
|
-
if (columns[0]?.field !== 'sys.title') {
|
|
223
|
-
throw formatError('The title field must be the first saved view column', code);
|
|
224
|
-
}
|
|
225
|
-
let sort;
|
|
226
|
-
if (value.sort !== undefined) {
|
|
227
|
-
if (!Array.isArray(value.sort) || value.sort.length > MAX_SAVED_VIEW_SORTS) {
|
|
228
|
-
throw formatError(`Saved view config supports at most ${MAX_SAVED_VIEW_SORTS} sort fields`, code);
|
|
229
|
-
}
|
|
230
|
-
const sortFields = new Set();
|
|
231
|
-
sort = value.sort.map((item, index) => {
|
|
232
|
-
if (!isPlainRecord(item)) {
|
|
233
|
-
throw formatError('Saved view sort entries must be objects', code, { index });
|
|
234
|
-
}
|
|
235
|
-
assertAllowedKeys(item, ['field', 'direction'], 'Saved view sort', code);
|
|
236
|
-
if (!isSavedViewFieldRef(item.field)) {
|
|
237
|
-
throw formatError('Saved view config contains an invalid sort field', code, { index, field: item.field });
|
|
238
|
-
}
|
|
239
|
-
if (item.direction !== 'asc' && item.direction !== 'desc') {
|
|
240
|
-
throw formatError('Saved view sort direction must be asc or desc', code, { index });
|
|
241
|
-
}
|
|
242
|
-
if (sortFields.has(item.field)) {
|
|
243
|
-
throw formatError('Saved view sort fields must be unique', code, { index, field: item.field });
|
|
244
|
-
}
|
|
245
|
-
sortFields.add(item.field);
|
|
246
|
-
return { field: item.field, direction: item.direction };
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
return {
|
|
250
|
-
version: SAVED_VIEW_CONFIG_VERSION,
|
|
251
|
-
layout: 'table',
|
|
252
|
-
columns,
|
|
253
|
-
...(sort === undefined ? {} : { sort }),
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
export function parseSavedViewConfig(raw) {
|
|
257
|
-
return validateSavedViewConfig(parseStoredJson(raw, 'Saved view config_json', 'SAVED_VIEW_INVALID_CONFIG'));
|
|
258
|
-
}
|
|
259
|
-
export function serializeSavedViewConfig(value) {
|
|
260
|
-
return JSON.stringify(validateSavedViewConfig(value));
|
|
36
|
+
return parsed.data;
|
|
261
37
|
}
|
|
262
|
-
export function
|
|
263
|
-
return
|
|
38
|
+
export function parseSavedViewFilter(raw) {
|
|
39
|
+
return validateSavedViewFilter(parseJson(raw, 'Saved view filter_json', 'SAVED_VIEW_INVALID_FILTER'));
|
|
264
40
|
}
|
|
265
|
-
export function
|
|
266
|
-
return
|
|
267
|
-
version: SAVED_VIEW_CONFIG_VERSION,
|
|
268
|
-
layout: 'table',
|
|
269
|
-
columns: [{ field: 'sys.title' }, { field: 'sys.updated_at' }],
|
|
270
|
-
sort: [{ field: 'sys.updated_at', direction: 'desc' }],
|
|
271
|
-
};
|
|
41
|
+
export function parseSavedViewConfig(raw) {
|
|
42
|
+
return validateSavedViewConfig(parseJson(raw, 'Saved view config_json', 'SAVED_VIEW_INVALID_CONFIG'));
|
|
272
43
|
}
|
|
273
|
-
|
|
274
|
-
const id = row.id.trim();
|
|
275
|
-
const userId = row.user_id?.trim() ?? '';
|
|
276
|
-
const name = row.name?.trim() ?? '';
|
|
277
|
-
if (!id || !userId || !name || name.length > 120) {
|
|
278
|
-
throw formatError('c_saved_view row is missing a valid id, user_id, or name', 'SAVED_VIEW_INVALID_ROW', {
|
|
279
|
-
id: row.id,
|
|
280
|
-
userId: row.user_id,
|
|
281
|
-
name: row.name,
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
if (row.view_type !== 'table') {
|
|
285
|
-
throw formatError(`Unsupported c_saved_view view_type: ${String(row.view_type)}`, 'SAVED_VIEW_UNSUPPORTED_LAYOUT');
|
|
286
|
-
}
|
|
287
|
-
const filter = parseSavedViewFilter(row.filter_json);
|
|
288
|
-
const config = parseSavedViewConfig(row.config_json);
|
|
289
|
-
if (config.layout !== row.view_type) {
|
|
290
|
-
throw formatError('c_saved_view view_type must match config_json.layout', 'SAVED_VIEW_INVALID_ROW');
|
|
291
|
-
}
|
|
44
|
+
function baseFields(row) {
|
|
292
45
|
return {
|
|
293
|
-
id,
|
|
294
|
-
userId,
|
|
295
|
-
name,
|
|
46
|
+
id: row.id.trim(),
|
|
47
|
+
userId: row.user_id?.trim() ?? '',
|
|
48
|
+
name: row.name?.trim() ?? '',
|
|
296
49
|
emoji: row.emoji,
|
|
297
|
-
filter,
|
|
298
|
-
config,
|
|
299
50
|
templateId: row.template_id,
|
|
300
51
|
groupId: row.group_id,
|
|
301
52
|
sortRank: row.sort_rank,
|
|
@@ -304,3 +55,36 @@ export function mapSavedViewRow(row) {
|
|
|
304
55
|
updatedAt: row.updated_at,
|
|
305
56
|
};
|
|
306
57
|
}
|
|
58
|
+
export function mapSavedViewRow(row) {
|
|
59
|
+
const base = baseFields(row);
|
|
60
|
+
if (!base.id || !base.userId || !base.name || base.name.length > 120) {
|
|
61
|
+
throw invalid('SAVED_VIEW_INVALID_ROW', 'c_saved_view row is missing a valid id, user_id, or name');
|
|
62
|
+
}
|
|
63
|
+
const filter = parseSavedViewFilter(row.filter_json);
|
|
64
|
+
const config = parseSavedViewConfig(row.config_json);
|
|
65
|
+
return { ...base, layout: config.layout, filter, config };
|
|
66
|
+
}
|
|
67
|
+
/** Parses a stored row without throwing, so catalogs and `view get` can surface bad rows. */
|
|
68
|
+
export function inspectSavedViewRow(row) {
|
|
69
|
+
try {
|
|
70
|
+
return { valid: true, view: mapSavedViewRow(row) };
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
const dinoxError = error instanceof DinoxError ? error : invalid('SAVED_VIEW_INVALID_ROW', String(error));
|
|
74
|
+
const issues = dinoxError.details?.issues;
|
|
75
|
+
return {
|
|
76
|
+
valid: false,
|
|
77
|
+
view: {
|
|
78
|
+
...baseFields(row),
|
|
79
|
+
viewType: row.view_type,
|
|
80
|
+
filterJson: row.filter_json,
|
|
81
|
+
configJson: row.config_json,
|
|
82
|
+
},
|
|
83
|
+
error: {
|
|
84
|
+
code: dinoxError.code,
|
|
85
|
+
message: dinoxError.message,
|
|
86
|
+
...(issues === undefined ? {} : { issues }),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
-
import { type
|
|
3
|
-
import { type
|
|
2
|
+
import { type PropertyOption, type SavedViewAnalyticsRow, type SavedViewComputedColumn, type SavedViewLayout, type SavedViewTableColumnKey } from '../../vendor/electron/shared/index.js';
|
|
3
|
+
import { type SavedViewConfig, type SavedViewFilter, type SavedViewInspection } from './model.js';
|
|
4
|
+
export { listSavedViewFields } from './fields.js';
|
|
4
5
|
export type SavedViewCatalogItem = {
|
|
5
6
|
id: string;
|
|
6
7
|
name: string;
|
|
7
8
|
emoji: string | null;
|
|
9
|
+
layout: string | null;
|
|
8
10
|
templateId: string | null;
|
|
9
11
|
groupId: string | null;
|
|
10
12
|
sortRank: number | null;
|
|
@@ -17,39 +19,24 @@ export type SavedViewCatalogItem = {
|
|
|
17
19
|
message: string;
|
|
18
20
|
};
|
|
19
21
|
};
|
|
20
|
-
export type SavedViewPageRow = {
|
|
21
|
-
id: string;
|
|
22
|
-
title: string | null;
|
|
23
|
-
created_at: string | null;
|
|
24
|
-
updated_at: string | null;
|
|
25
|
-
tags: string | null;
|
|
26
|
-
zettel_boxes: string | null;
|
|
27
|
-
source: string | null;
|
|
28
|
-
type: string | null;
|
|
29
|
-
starred_at: string | null;
|
|
30
|
-
properties: string | null;
|
|
31
|
-
};
|
|
32
|
-
export type SavedViewQueryScope = {
|
|
33
|
-
definition: SavedViewDefinition;
|
|
34
|
-
semantics: ReadonlyMap<SavedViewFieldRef, SavedViewFieldSemantic>;
|
|
35
|
-
where: SavedViewSqlFragment;
|
|
36
|
-
order: SavedViewSqlFragment;
|
|
37
|
-
};
|
|
38
22
|
export type SavedViewProjectedRow = {
|
|
39
23
|
id: string;
|
|
40
24
|
title: string | null;
|
|
41
|
-
values: Partial<Record<
|
|
42
|
-
displayValues?: Partial<Record<
|
|
25
|
+
values: Partial<Record<SavedViewTableColumnKey, unknown>>;
|
|
26
|
+
displayValues?: Partial<Record<SavedViewTableColumnKey, unknown>>;
|
|
27
|
+
};
|
|
28
|
+
export type SavedViewProjectedColumn = {
|
|
29
|
+
field: SavedViewTableColumnKey;
|
|
30
|
+
type: string;
|
|
31
|
+
propertyType: string | null;
|
|
32
|
+
dateMode: string | null;
|
|
33
|
+
displayOnly?: boolean;
|
|
34
|
+
computed?: SavedViewComputedColumn;
|
|
35
|
+
options?: readonly PropertyOption[];
|
|
43
36
|
};
|
|
44
37
|
export type SavedViewQueryResult = {
|
|
45
|
-
view:
|
|
46
|
-
columns:
|
|
47
|
-
field: SavedViewFieldRef;
|
|
48
|
-
type: SavedViewFieldSemantic['type'];
|
|
49
|
-
propertyType: SavedViewFieldSemantic['propertyType'] | null;
|
|
50
|
-
dateMode: SavedViewFieldSemantic['dateMode'] | null;
|
|
51
|
-
options?: readonly PropertyOption[];
|
|
52
|
-
}>;
|
|
38
|
+
view: SavedViewInspection['view'];
|
|
39
|
+
columns: SavedViewProjectedColumn[];
|
|
53
40
|
totalCount: number;
|
|
54
41
|
offset: number;
|
|
55
42
|
limit: number;
|
|
@@ -57,67 +44,82 @@ export type SavedViewQueryResult = {
|
|
|
57
44
|
hasMore: boolean;
|
|
58
45
|
rows: SavedViewProjectedRow[];
|
|
59
46
|
};
|
|
60
|
-
export type
|
|
61
|
-
|
|
62
|
-
name: string;
|
|
47
|
+
export type SavedViewWriteInput = {
|
|
48
|
+
name?: string;
|
|
63
49
|
emoji?: string | null;
|
|
64
50
|
filter?: SavedViewFilter;
|
|
65
51
|
config?: SavedViewConfig;
|
|
52
|
+
layout?: SavedViewLayout;
|
|
66
53
|
templateId?: string | null;
|
|
67
54
|
groupId?: string | null;
|
|
68
55
|
sortRank?: number | null;
|
|
69
56
|
pinnedAt?: string | null;
|
|
70
|
-
updatedAt?: string;
|
|
71
57
|
};
|
|
72
|
-
export type
|
|
58
|
+
export type CreateSavedViewInput = SavedViewWriteInput & {
|
|
59
|
+
userId: string;
|
|
60
|
+
name: string;
|
|
61
|
+
};
|
|
62
|
+
export type UpdateSavedViewInput = SavedViewWriteInput & {
|
|
73
63
|
id: string;
|
|
74
64
|
userId: string;
|
|
75
|
-
name?: string;
|
|
76
|
-
emoji?: string | null;
|
|
77
|
-
filter?: SavedViewFilter;
|
|
78
|
-
config?: SavedViewConfig;
|
|
79
|
-
templateId?: string | null;
|
|
80
|
-
groupId?: string | null;
|
|
81
|
-
sortRank?: number | null;
|
|
82
|
-
pinnedAt?: string | null;
|
|
83
|
-
updatedAt?: string;
|
|
84
65
|
};
|
|
85
66
|
export type DeleteSavedViewInput = {
|
|
86
67
|
id: string;
|
|
87
68
|
userId: string;
|
|
88
|
-
updatedAt?: string;
|
|
89
69
|
};
|
|
90
|
-
export type
|
|
70
|
+
export type SavedViewMutationPreview = {
|
|
91
71
|
action: 'create' | 'update' | 'delete';
|
|
92
|
-
|
|
93
|
-
|
|
72
|
+
before: SavedViewInspection | null;
|
|
73
|
+
after: SavedViewInspection | null;
|
|
94
74
|
};
|
|
95
75
|
export declare function listSavedViews(db: AbstractPowerSyncDatabase, input: {
|
|
96
76
|
userId: string;
|
|
97
77
|
limit?: number;
|
|
98
78
|
}): Promise<SavedViewCatalogItem[]>;
|
|
79
|
+
/** Reads one view without throwing on stored rows the CLI cannot parse (see `valid`). */
|
|
99
80
|
export declare function getSavedView(db: AbstractPowerSyncDatabase, input: {
|
|
100
81
|
id: string;
|
|
101
82
|
userId: string;
|
|
102
|
-
}): Promise<
|
|
103
|
-
|
|
83
|
+
}): Promise<SavedViewInspection>;
|
|
84
|
+
/** Row-level evaluation, aligned with desktop: non-finite side or division by zero → null. */
|
|
85
|
+
export declare function evaluateComputedColumn(properties: Record<string, unknown>, column: SavedViewComputedColumn): number | null;
|
|
86
|
+
export declare function querySavedView(db: AbstractPowerSyncDatabase, input: {
|
|
104
87
|
viewId: string;
|
|
105
88
|
userId: string;
|
|
106
|
-
|
|
89
|
+
limit?: number;
|
|
90
|
+
offset?: number;
|
|
91
|
+
timeZone?: string;
|
|
92
|
+
}): Promise<SavedViewQueryResult>;
|
|
107
93
|
export declare function countSavedViewRows(db: AbstractPowerSyncDatabase, input: {
|
|
108
94
|
viewId: string;
|
|
109
95
|
userId: string;
|
|
110
|
-
|
|
111
|
-
|
|
96
|
+
timeZone?: string;
|
|
97
|
+
}): Promise<number>;
|
|
98
|
+
export declare function getSavedViewAnalytics(db: AbstractPowerSyncDatabase, input: {
|
|
99
|
+
viewId: string;
|
|
100
|
+
userId: string;
|
|
101
|
+
timeZone?: string;
|
|
102
|
+
}): Promise<SavedViewAnalyticsRow & {
|
|
103
|
+
config: Pick<SavedViewConfig, 'stats' | 'charts' | 'summary' | 'computedColumns'>;
|
|
104
|
+
}>;
|
|
105
|
+
export type NoteDetailSection = {
|
|
112
106
|
viewId: string;
|
|
107
|
+
name: string;
|
|
108
|
+
emoji: string | null;
|
|
109
|
+
totalCount: number;
|
|
110
|
+
columns: SavedViewProjectedColumn[];
|
|
111
|
+
rows: SavedViewProjectedRow[];
|
|
112
|
+
};
|
|
113
|
+
/** Saved views whose `noteDetail` rule matches the note, with `$self` bound to it. */
|
|
114
|
+
export declare function getNoteDetailSections(db: AbstractPowerSyncDatabase, input: {
|
|
115
|
+
noteId: string;
|
|
113
116
|
userId: string;
|
|
117
|
+
timeZone?: string;
|
|
114
118
|
limit?: number;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
export declare function
|
|
118
|
-
export declare function
|
|
119
|
-
export declare function
|
|
120
|
-
export declare function
|
|
121
|
-
export declare function
|
|
122
|
-
export declare function inspectDeleteSavedView(db: AbstractPowerSyncDatabase, input: DeleteSavedViewInput): Promise<SavedViewMutationPlan>;
|
|
123
|
-
export declare function deleteSavedView(db: AbstractPowerSyncDatabase, input: DeleteSavedViewInput): Promise<SavedViewDefinition>;
|
|
119
|
+
}): Promise<NoteDetailSection[]>;
|
|
120
|
+
export declare function createSavedView(db: AbstractPowerSyncDatabase, input: CreateSavedViewInput): Promise<SavedViewInspection>;
|
|
121
|
+
export declare function updateSavedView(db: AbstractPowerSyncDatabase, input: UpdateSavedViewInput): Promise<SavedViewInspection>;
|
|
122
|
+
export declare function deleteSavedView(db: AbstractPowerSyncDatabase, input: DeleteSavedViewInput): Promise<SavedViewInspection>;
|
|
123
|
+
export declare function previewCreateSavedView(db: AbstractPowerSyncDatabase, input: CreateSavedViewInput): Promise<SavedViewMutationPreview>;
|
|
124
|
+
export declare function previewUpdateSavedView(db: AbstractPowerSyncDatabase, input: UpdateSavedViewInput): Promise<SavedViewMutationPreview>;
|
|
125
|
+
export declare function previewDeleteSavedView(db: AbstractPowerSyncDatabase, input: DeleteSavedViewInput): Promise<SavedViewMutationPreview>;
|