@dinoxx/dinox-cli 1.1.0 → 2.0.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/README.md +48 -4
- package/dist/cli.js +4 -0
- package/dist/commands/auth/index.js +1 -2
- package/dist/commands/boxes/index.js +37 -13
- package/dist/commands/boxes/operations.d.ts +3 -3
- package/dist/commands/boxes/operations.js +51 -28
- package/dist/commands/info/index.js +30 -5
- package/dist/commands/meta/repo.js +2 -1
- package/dist/commands/notes/actions.js +20 -37
- package/dist/commands/notes/bulk.d.ts +2 -2
- package/dist/commands/notes/bulk.js +3 -18
- package/dist/commands/notes/contentPatch.d.ts +5 -1
- package/dist/commands/notes/contentPatch.js +12 -1
- package/dist/commands/notes/contentPatchCommand.d.ts +3 -0
- package/dist/commands/notes/contentPatchCommand.js +29 -8
- package/dist/commands/notes/descriptors.js +19 -16
- package/dist/commands/notes/export.js +3 -0
- package/dist/commands/notes/repo.d.ts +9 -0
- package/dist/commands/notes/repo.js +65 -15
- package/dist/commands/notes/searchOutput.d.ts +2 -1
- package/dist/commands/notes/searchOutput.js +2 -1
- package/dist/commands/notes/searchSql.js +9 -3
- package/dist/commands/prompt/index.d.ts +1 -0
- package/dist/commands/prompt/index.js +26 -11
- package/dist/commands/runtime.js +17 -6
- package/dist/commands/savedViews/index.d.ts +7 -0
- package/dist/commands/savedViews/index.js +458 -0
- package/dist/commands/savedViews/model.d.ts +87 -0
- package/dist/commands/savedViews/model.js +306 -0
- package/dist/commands/savedViews/repo.d.ts +123 -0
- package/dist/commands/savedViews/repo.js +512 -0
- package/dist/commands/savedViews/semantics.d.ts +46 -0
- package/dist/commands/savedViews/semantics.js +738 -0
- package/dist/commands/schema/renderSkills.d.ts +2 -0
- package/dist/commands/schema/renderSkills.js +69 -10
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/skills/index.d.ts +54 -0
- package/dist/commands/skills/index.js +468 -0
- package/dist/commands/storage/index.js +2 -2
- package/dist/commands/sync.d.ts +23 -1
- package/dist/commands/sync.js +100 -58
- package/dist/commands/tags/index.js +30 -11
- package/dist/commands/tags/operations.d.ts +3 -3
- package/dist/commands/tags/operations.js +45 -20
- package/dist/commands/templates/model.d.ts +50 -0
- package/dist/commands/templates/model.js +239 -0
- package/dist/commands/todo/index.js +3 -3
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +46 -9
- package/dist/commands/writeConfirmation.d.ts +13 -0
- package/dist/commands/writeConfirmation.js +54 -0
- package/dist/config/resolve.js +5 -10
- package/dist/config/types.d.ts +3 -6
- package/dist/constants/links.d.ts +18 -1
- package/dist/constants/links.js +26 -1
- package/dist/daemon/client.d.ts +2 -2
- package/dist/daemon/client.js +106 -52
- package/dist/daemon/index.d.ts +5 -3
- package/dist/daemon/index.js +28 -5
- package/dist/daemon/lifecycle.d.ts +10 -3
- package/dist/daemon/lifecycle.js +54 -11
- package/dist/daemon/pid.d.ts +1 -1
- package/dist/daemon/pid.js +6 -3
- package/dist/dinox.js +9 -1
- package/dist/powersync/exclusiveRuntime.d.ts +2 -0
- package/dist/powersync/exclusiveRuntime.js +28 -4
- package/dist/powersync/runtime.d.ts +4 -2
- package/dist/powersync/runtime.js +43 -5
- package/dist/powersync/schema/content.d.ts +1 -78
- package/dist/powersync/schema/content.js +1 -78
- package/dist/powersync/schema/electronCanonical.d.ts +1561 -0
- package/dist/powersync/schema/electronCanonical.js +1154 -0
- package/dist/powersync/schema/index.d.ts +573 -129
- package/dist/powersync/schema/index.js +3 -32
- package/dist/powersync/schema/local.d.ts +1 -56
- package/dist/powersync/schema/local.js +1 -60
- package/dist/powersync/schema/note.d.ts +1 -35
- package/dist/powersync/schema/note.js +1 -35
- package/dist/powersync/schema/notesExtras.d.ts +1 -62
- package/dist/powersync/schema/notesExtras.js +1 -71
- package/dist/powersync/schema/projects.d.ts +1 -104
- package/dist/powersync/schema/projects.js +1 -104
- package/dist/powersync/schema/savedViews.d.ts +1 -0
- package/dist/powersync/schema/savedViews.js +1 -0
- package/dist/powersync/schema/storage.d.ts +1 -24
- package/dist/powersync/schema/storage.js +1 -24
- package/dist/powersync/schema/tags.d.ts +1 -37
- package/dist/powersync/schema/tags.js +1 -37
- package/dist/powersync/syncGate.d.ts +22 -3
- package/dist/powersync/syncGate.js +4 -1
- package/dist/powersync/syncPass.d.ts +2 -0
- package/dist/powersync/syncPass.js +15 -4
- package/dist/powersync/tokenIndex.d.ts +2 -0
- package/dist/powersync/tokenIndex.js +60 -18
- package/dist/powersync/uploader.d.ts +16 -2
- package/dist/powersync/uploader.js +152 -158
- package/dist/skills/check.d.ts +1 -0
- package/dist/skills/check.js +538 -70
- package/dist/skills/retired.d.ts +60 -0
- package/dist/skills/retired.js +58 -0
- package/dist/utils/deadline.d.ts +8 -0
- package/dist/utils/deadline.js +31 -0
- package/dist/utils/dryRun.d.ts +20 -1
- package/dist/utils/dryRun.js +22 -3
- package/dist/utils/errors.d.ts +2 -0
- package/dist/utils/errors.js +5 -3
- package/dist/utils/processExit.d.ts +1 -0
- package/dist/utils/processExit.js +33 -0
- package/dist/utils/updateNotice.d.ts +1 -0
- package/dist/utils/updateNotice.js +1 -1
- package/package.json +10 -5
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export declare const SAVED_VIEW_FILTER_VERSION = 1;
|
|
2
|
+
export declare const SAVED_VIEW_CONFIG_VERSION = 1;
|
|
3
|
+
export declare const MAX_SAVED_VIEW_FILTER_DEPTH = 8;
|
|
4
|
+
export declare const MAX_SAVED_VIEW_FILTER_NODES = 100;
|
|
5
|
+
export declare const MAX_SAVED_VIEW_FILTER_VALUE_LENGTH = 2000;
|
|
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
|
+
};
|
|
63
|
+
export type SavedViewDefinition = {
|
|
64
|
+
id: string;
|
|
65
|
+
userId: string;
|
|
66
|
+
name: string;
|
|
67
|
+
emoji: string | null;
|
|
68
|
+
filter: SavedViewFilter;
|
|
69
|
+
config: SavedViewConfig;
|
|
70
|
+
templateId: string | null;
|
|
71
|
+
groupId: string | null;
|
|
72
|
+
sortRank: number | null;
|
|
73
|
+
pinnedAt: string | null;
|
|
74
|
+
createdAt: string | null;
|
|
75
|
+
updatedAt: string | null;
|
|
76
|
+
};
|
|
77
|
+
export declare function isNotePropertyKeyValid(value: unknown): value is string;
|
|
78
|
+
export declare function isSavedViewFieldRef(value: unknown): value is SavedViewFieldRef;
|
|
79
|
+
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
|
+
export declare function validateSavedViewConfig(value: unknown): SavedViewConfig;
|
|
83
|
+
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
|
+
export declare function mapSavedViewRow(row: SavedViewRow): SavedViewDefinition;
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { DinoxError } from '../../utils/errors.js';
|
|
2
|
+
export const SAVED_VIEW_FILTER_VERSION = 1;
|
|
3
|
+
export const SAVED_VIEW_CONFIG_VERSION = 1;
|
|
4
|
+
export const MAX_SAVED_VIEW_FILTER_DEPTH = 8;
|
|
5
|
+
export const MAX_SAVED_VIEW_FILTER_NODES = 100;
|
|
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
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function isPlainRecord(value) {
|
|
50
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
function assertAllowedKeys(record, allowed, label, code) {
|
|
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) {
|
|
60
|
+
const value = raw?.trim() ?? '';
|
|
61
|
+
if (!value) {
|
|
62
|
+
throw formatError(`${label} is required`, code);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(value);
|
|
66
|
+
}
|
|
67
|
+
catch (cause) {
|
|
68
|
+
throw formatError(`${label} must be valid JSON`, code, {
|
|
69
|
+
cause: cause instanceof Error ? cause.message : String(cause),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
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
|
+
export function validateSavedViewFilter(value) {
|
|
162
|
+
const code = 'SAVED_VIEW_INVALID_FILTER';
|
|
163
|
+
if (!isPlainRecord(value)) {
|
|
164
|
+
throw formatError('Saved view filter must be a versioned object', code);
|
|
165
|
+
}
|
|
166
|
+
if (value.version !== SAVED_VIEW_FILTER_VERSION) {
|
|
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));
|
|
186
|
+
}
|
|
187
|
+
export function validateSavedViewConfig(value) {
|
|
188
|
+
const code = 'SAVED_VIEW_INVALID_CONFIG';
|
|
189
|
+
if (!isPlainRecord(value)) {
|
|
190
|
+
throw formatError('Saved view config must be a versioned object', code);
|
|
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);
|
|
206
|
+
}
|
|
207
|
+
const columnFields = new Set();
|
|
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));
|
|
261
|
+
}
|
|
262
|
+
export function createDefaultSavedViewFilter() {
|
|
263
|
+
return { version: SAVED_VIEW_FILTER_VERSION, root: null };
|
|
264
|
+
}
|
|
265
|
+
export function createDefaultSavedViewConfig() {
|
|
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
|
+
};
|
|
272
|
+
}
|
|
273
|
+
export function mapSavedViewRow(row) {
|
|
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
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
id,
|
|
294
|
+
userId,
|
|
295
|
+
name,
|
|
296
|
+
emoji: row.emoji,
|
|
297
|
+
filter,
|
|
298
|
+
config,
|
|
299
|
+
templateId: row.template_id,
|
|
300
|
+
groupId: row.group_id,
|
|
301
|
+
sortRank: row.sort_rank,
|
|
302
|
+
pinnedAt: row.pinned_at,
|
|
303
|
+
createdAt: row.created_at,
|
|
304
|
+
updatedAt: row.updated_at,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
+
import { type SavedViewConfig, type SavedViewDefinition, type SavedViewFieldRef, type SavedViewFilter } from './model.js';
|
|
3
|
+
import { type PropertyOption, type SavedViewFieldCatalogItem, type SavedViewFieldSemantic, type SavedViewSqlFragment } from './semantics.js';
|
|
4
|
+
export type SavedViewCatalogItem = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
emoji: string | null;
|
|
8
|
+
templateId: string | null;
|
|
9
|
+
groupId: string | null;
|
|
10
|
+
sortRank: number | null;
|
|
11
|
+
pinnedAt: string | null;
|
|
12
|
+
createdAt: string | null;
|
|
13
|
+
updatedAt: string | null;
|
|
14
|
+
valid: boolean;
|
|
15
|
+
error?: {
|
|
16
|
+
code: string;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
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
|
+
export type SavedViewProjectedRow = {
|
|
39
|
+
id: string;
|
|
40
|
+
title: string | null;
|
|
41
|
+
values: Partial<Record<SavedViewFieldRef, unknown>>;
|
|
42
|
+
displayValues?: Partial<Record<SavedViewFieldRef, unknown>>;
|
|
43
|
+
};
|
|
44
|
+
export type SavedViewQueryResult = {
|
|
45
|
+
view: SavedViewDefinition;
|
|
46
|
+
columns: Array<{
|
|
47
|
+
field: SavedViewFieldRef;
|
|
48
|
+
type: SavedViewFieldSemantic['type'];
|
|
49
|
+
propertyType: SavedViewFieldSemantic['propertyType'] | null;
|
|
50
|
+
dateMode: SavedViewFieldSemantic['dateMode'] | null;
|
|
51
|
+
options?: readonly PropertyOption[];
|
|
52
|
+
}>;
|
|
53
|
+
totalCount: number;
|
|
54
|
+
offset: number;
|
|
55
|
+
limit: number;
|
|
56
|
+
nextOffset: number | null;
|
|
57
|
+
hasMore: boolean;
|
|
58
|
+
rows: SavedViewProjectedRow[];
|
|
59
|
+
};
|
|
60
|
+
export type CreateSavedViewInput = {
|
|
61
|
+
userId: string;
|
|
62
|
+
name: string;
|
|
63
|
+
emoji?: string | null;
|
|
64
|
+
filter?: SavedViewFilter;
|
|
65
|
+
config?: SavedViewConfig;
|
|
66
|
+
templateId?: string | null;
|
|
67
|
+
groupId?: string | null;
|
|
68
|
+
sortRank?: number | null;
|
|
69
|
+
pinnedAt?: string | null;
|
|
70
|
+
updatedAt?: string;
|
|
71
|
+
};
|
|
72
|
+
export type UpdateSavedViewInput = {
|
|
73
|
+
id: string;
|
|
74
|
+
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
|
+
};
|
|
85
|
+
export type DeleteSavedViewInput = {
|
|
86
|
+
id: string;
|
|
87
|
+
userId: string;
|
|
88
|
+
updatedAt?: string;
|
|
89
|
+
};
|
|
90
|
+
export type SavedViewMutationPlan = {
|
|
91
|
+
action: 'create' | 'update' | 'delete';
|
|
92
|
+
target: SavedViewDefinition | null;
|
|
93
|
+
changes: Record<string, unknown>;
|
|
94
|
+
};
|
|
95
|
+
export declare function listSavedViews(db: AbstractPowerSyncDatabase, input: {
|
|
96
|
+
userId: string;
|
|
97
|
+
limit?: number;
|
|
98
|
+
}): Promise<SavedViewCatalogItem[]>;
|
|
99
|
+
export declare function getSavedView(db: AbstractPowerSyncDatabase, input: {
|
|
100
|
+
id: string;
|
|
101
|
+
userId: string;
|
|
102
|
+
}): Promise<SavedViewDefinition>;
|
|
103
|
+
export declare function resolveSavedViewQueryScope(db: AbstractPowerSyncDatabase, input: {
|
|
104
|
+
viewId: string;
|
|
105
|
+
userId: string;
|
|
106
|
+
}): Promise<SavedViewQueryScope>;
|
|
107
|
+
export declare function countSavedViewRows(db: AbstractPowerSyncDatabase, input: {
|
|
108
|
+
viewId: string;
|
|
109
|
+
userId: string;
|
|
110
|
+
}, scope?: SavedViewQueryScope): Promise<number>;
|
|
111
|
+
export declare function querySavedView(db: AbstractPowerSyncDatabase, input: {
|
|
112
|
+
viewId: string;
|
|
113
|
+
userId: string;
|
|
114
|
+
limit?: number;
|
|
115
|
+
offset?: number;
|
|
116
|
+
}): Promise<SavedViewQueryResult>;
|
|
117
|
+
export declare function getSavedViewFieldCatalog(db: AbstractPowerSyncDatabase, userId: string): Promise<SavedViewFieldCatalogItem[]>;
|
|
118
|
+
export declare function inspectCreateSavedView(db: AbstractPowerSyncDatabase, input: CreateSavedViewInput): Promise<SavedViewMutationPlan>;
|
|
119
|
+
export declare function createSavedView(db: AbstractPowerSyncDatabase, input: CreateSavedViewInput): Promise<SavedViewDefinition>;
|
|
120
|
+
export declare function inspectUpdateSavedView(db: AbstractPowerSyncDatabase, input: UpdateSavedViewInput): Promise<SavedViewMutationPlan>;
|
|
121
|
+
export declare function updateSavedView(db: AbstractPowerSyncDatabase, input: UpdateSavedViewInput): Promise<SavedViewDefinition>;
|
|
122
|
+
export declare function inspectDeleteSavedView(db: AbstractPowerSyncDatabase, input: DeleteSavedViewInput): Promise<SavedViewMutationPlan>;
|
|
123
|
+
export declare function deleteSavedView(db: AbstractPowerSyncDatabase, input: DeleteSavedViewInput): Promise<SavedViewDefinition>;
|