@dinoxx/dinox-cli 2.2.0 → 2.4.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 +17 -3
- package/dist/cli.js +6 -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/gateway/index.d.ts +4 -0
- package/dist/commands/gateway/index.js +300 -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 +33 -5
- package/dist/commands/schema/spec.js +6 -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/config/paths.d.ts +1 -0
- package/dist/config/paths.js +1 -1
- package/dist/daemon/server.d.ts +1 -0
- package/dist/daemon/server.js +31 -0
- package/dist/gateway/adapters/acp.d.ts +42 -0
- package/dist/gateway/adapters/acp.js +285 -0
- package/dist/gateway/adapters/echo.d.ts +12 -0
- package/dist/gateway/adapters/echo.js +51 -0
- package/dist/gateway/adapters/index.d.ts +9 -0
- package/dist/gateway/adapters/index.js +30 -0
- package/dist/gateway/assistantProjection.d.ts +32 -0
- package/dist/gateway/assistantProjection.js +193 -0
- package/dist/gateway/config.d.ts +50 -0
- package/dist/gateway/config.js +253 -0
- package/dist/gateway/detect.d.ts +40 -0
- package/dist/gateway/detect.js +145 -0
- package/dist/gateway/deviceHost.d.ts +82 -0
- package/dist/gateway/deviceHost.js +530 -0
- package/dist/gateway/mcpServer.d.ts +4 -0
- package/dist/gateway/mcpServer.js +118 -0
- package/dist/gateway/protocol.d.ts +29 -0
- package/dist/gateway/protocol.js +127 -0
- package/dist/gateway/runExecutor.d.ts +67 -0
- package/dist/gateway/runExecutor.js +316 -0
- package/dist/gateway/service.d.ts +26 -0
- package/dist/gateway/service.js +82 -0
- package/dist/gateway/streamPublisher.d.ts +33 -0
- package/dist/gateway/streamPublisher.js +93 -0
- package/dist/gateway/threadStore.d.ts +157 -0
- package/dist/gateway/threadStore.js +430 -0
- package/dist/gateway/types.d.ts +117 -0
- package/dist/gateway/types.js +9 -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 +1549 -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 +1120 -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 +20 -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 +328 -0
- package/dist/vendor/electron/shared/note-properties.js +1027 -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 +1716 -0
- package/dist/vendor/electron/shared/powersync-read-models.js +1 -0
- package/dist/vendor/electron/shared/remote-device-agent.d.ts +373 -0
- package/dist/vendor/electron/shared/remote-device-agent.js +191 -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 +10 -4
- package/dist/commands/savedViews/semantics.d.ts +0 -46
- package/dist/commands/savedViews/semantics.js +0 -738
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
+
import { type ActivePropertyType, type NotePropertiesFillEmptyResult, type NotePropertiesPatch, type NoteProperties, type NotePropertySlotOperation, type NotePropertyValue, type PropertyDef, type PropertyDefConfig, type PropertyDefMigrateLegacyOptionsResult, type PropertyDefSource, type PropertyOptionInput, type PropertyType } from '../../vendor/electron/shared/index.js';
|
|
3
|
+
export declare function listProperties(db: AbstractPowerSyncDatabase, input: {
|
|
4
|
+
userId: string;
|
|
5
|
+
limit?: number;
|
|
6
|
+
}): Promise<PropertyDef[]>;
|
|
7
|
+
/** Resolves a definition by id, falling back to key (keys are unique among active defs). */
|
|
8
|
+
export declare function findProperty(db: AbstractPowerSyncDatabase, input: {
|
|
9
|
+
userId: string;
|
|
10
|
+
ref: string;
|
|
11
|
+
}): Promise<PropertyDef | null>;
|
|
12
|
+
export declare function requireProperty(db: AbstractPowerSyncDatabase, input: {
|
|
13
|
+
userId: string;
|
|
14
|
+
ref: string;
|
|
15
|
+
}): Promise<PropertyDef>;
|
|
16
|
+
export type PropertyCreateInput = {
|
|
17
|
+
userId: string;
|
|
18
|
+
key: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
type?: ActivePropertyType;
|
|
21
|
+
optionItems?: PropertyOptionInput[];
|
|
22
|
+
uniqueIdPrefix?: string;
|
|
23
|
+
config?: PropertyDefConfig;
|
|
24
|
+
description?: string;
|
|
25
|
+
icon?: string;
|
|
26
|
+
source?: PropertyDefSource;
|
|
27
|
+
};
|
|
28
|
+
export type PropertyCreateResult = {
|
|
29
|
+
property: PropertyDef;
|
|
30
|
+
created: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare function createProperty(db: AbstractPowerSyncDatabase, input: PropertyCreateInput): Promise<PropertyCreateResult>;
|
|
33
|
+
export declare function previewCreateProperty(db: AbstractPowerSyncDatabase, input: PropertyCreateInput): Promise<PropertyCreateResult>;
|
|
34
|
+
export type OptionEdit = {
|
|
35
|
+
kind: 'add';
|
|
36
|
+
label: string;
|
|
37
|
+
color?: PropertyOptionInput['color'];
|
|
38
|
+
group?: PropertyOptionInput['group'];
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'archive' | 'restore';
|
|
41
|
+
ref: string;
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'rename';
|
|
44
|
+
ref: string;
|
|
45
|
+
label: string;
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'color';
|
|
48
|
+
ref: string;
|
|
49
|
+
color: NonNullable<PropertyOptionInput['color']>;
|
|
50
|
+
};
|
|
51
|
+
export type PropertyConfigureInput = {
|
|
52
|
+
userId: string;
|
|
53
|
+
ref: string;
|
|
54
|
+
name?: string | null;
|
|
55
|
+
description?: string | null;
|
|
56
|
+
icon?: string | null;
|
|
57
|
+
showOnCard?: boolean;
|
|
58
|
+
sortRank?: number | null;
|
|
59
|
+
options?: PropertyOptionInput[];
|
|
60
|
+
optionEdits?: OptionEdit[];
|
|
61
|
+
uniqueIdPrefix?: string;
|
|
62
|
+
repairInvalidOptions?: boolean;
|
|
63
|
+
config?: PropertyDefConfig | null;
|
|
64
|
+
};
|
|
65
|
+
/** Applies add/archive/restore/rename/color edits onto the complete current option list. */
|
|
66
|
+
export declare function applyOptionEdits(definition: PropertyDef, edits: readonly OptionEdit[]): PropertyOptionInput[];
|
|
67
|
+
export declare function configureProperty(db: AbstractPowerSyncDatabase, input: PropertyConfigureInput): Promise<{
|
|
68
|
+
property: PropertyDef;
|
|
69
|
+
changed: boolean;
|
|
70
|
+
}>;
|
|
71
|
+
export declare function previewConfigureProperty(db: AbstractPowerSyncDatabase, input: PropertyConfigureInput): Promise<{
|
|
72
|
+
property: PropertyDef;
|
|
73
|
+
changed: boolean;
|
|
74
|
+
before: PropertyDef;
|
|
75
|
+
}>;
|
|
76
|
+
export declare function deleteProperty(db: AbstractPowerSyncDatabase, input: {
|
|
77
|
+
userId: string;
|
|
78
|
+
ref: string;
|
|
79
|
+
}): Promise<PropertyDef>;
|
|
80
|
+
export declare function previewDeleteProperty(db: AbstractPowerSyncDatabase, input: {
|
|
81
|
+
userId: string;
|
|
82
|
+
ref: string;
|
|
83
|
+
}): Promise<PropertyDef>;
|
|
84
|
+
export declare function migrationAffectedCount(result: PropertyDefMigrateLegacyOptionsResult): number;
|
|
85
|
+
export declare function previewMigrateLegacyOptions(db: AbstractPowerSyncDatabase, userId: string): Promise<PropertyDefMigrateLegacyOptionsResult>;
|
|
86
|
+
/** Runs the migration and rolls it back unless it touches exactly `expectedCount` records. */
|
|
87
|
+
export declare function migrateLegacyOptions(db: AbstractPowerSyncDatabase, input: {
|
|
88
|
+
userId: string;
|
|
89
|
+
expectedCount: number;
|
|
90
|
+
}): Promise<PropertyDefMigrateLegacyOptionsResult>;
|
|
91
|
+
export type NotePropertyEntry = {
|
|
92
|
+
key: string;
|
|
93
|
+
name: string;
|
|
94
|
+
type: PropertyType;
|
|
95
|
+
/** False when the key has no active definition; its type is inferred from the value. */
|
|
96
|
+
defined: boolean;
|
|
97
|
+
propertyDefinitionId: string | null;
|
|
98
|
+
/** Null marks a declared-but-empty slot. */
|
|
99
|
+
value: NotePropertyValue | null;
|
|
100
|
+
display: unknown;
|
|
101
|
+
};
|
|
102
|
+
export type NotePropertiesView = {
|
|
103
|
+
noteId: string;
|
|
104
|
+
/** False when the stored JSON is corrupt; writes are refused until it is repaired. */
|
|
105
|
+
valid: boolean;
|
|
106
|
+
properties: Record<string, NotePropertyValue | null>;
|
|
107
|
+
entries: NotePropertyEntry[];
|
|
108
|
+
};
|
|
109
|
+
export declare function readNoteProperties(db: AbstractPowerSyncDatabase, input: {
|
|
110
|
+
userId: string;
|
|
111
|
+
noteId: string;
|
|
112
|
+
}): Promise<NotePropertiesView>;
|
|
113
|
+
export type NotePropertyMutationResult = {
|
|
114
|
+
changed: boolean;
|
|
115
|
+
before: NotePropertiesView;
|
|
116
|
+
after: NotePropertiesView;
|
|
117
|
+
};
|
|
118
|
+
export declare function setNoteProperties(db: AbstractPowerSyncDatabase, input: {
|
|
119
|
+
userId: string;
|
|
120
|
+
noteId: string;
|
|
121
|
+
patch: NotePropertiesPatch;
|
|
122
|
+
dryRun?: boolean;
|
|
123
|
+
}): Promise<NotePropertyMutationResult & {
|
|
124
|
+
result: import("../domain/run.js").DomainCommandResult<boolean>;
|
|
125
|
+
}>;
|
|
126
|
+
export declare function fillEmptyNoteProperties(db: AbstractPowerSyncDatabase, input: {
|
|
127
|
+
userId: string;
|
|
128
|
+
noteId: string;
|
|
129
|
+
values: NoteProperties;
|
|
130
|
+
dryRun?: boolean;
|
|
131
|
+
}): Promise<NotePropertyMutationResult & {
|
|
132
|
+
result: NotePropertiesFillEmptyResult;
|
|
133
|
+
}>;
|
|
134
|
+
export declare function updateNotePropertySlots(db: AbstractPowerSyncDatabase, input: {
|
|
135
|
+
userId: string;
|
|
136
|
+
noteId: string;
|
|
137
|
+
operation: NotePropertySlotOperation;
|
|
138
|
+
keys: string[];
|
|
139
|
+
dryRun?: boolean;
|
|
140
|
+
}): Promise<NotePropertyMutationResult & {
|
|
141
|
+
result: import("../domain/run.js").DomainCommandResult<boolean>;
|
|
142
|
+
}>;
|
|
143
|
+
export declare function reorderNotePropertySlots(db: AbstractPowerSyncDatabase, input: {
|
|
144
|
+
userId: string;
|
|
145
|
+
noteId: string;
|
|
146
|
+
orderedKeys: string[];
|
|
147
|
+
dryRun?: boolean;
|
|
148
|
+
}): Promise<NotePropertyMutationResult & {
|
|
149
|
+
result: import("../domain/run.js").DomainCommandResult<boolean>;
|
|
150
|
+
}>;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// Property definitions (`c_property_def` + companion `c_config_property` rows) and note
|
|
2
|
+
// property values (`c_note.properties`), mutated through the vendored desktop commands so
|
|
3
|
+
// option ids, unique-id counters, config envelopes, and value coercion match Dinox desktop.
|
|
4
|
+
import { DinoxError } from '../../utils/errors.js';
|
|
5
|
+
import { findPropertyOptionById, findPropertyOptionByLabel, formatPropertyValue, inferPropertyTypeFromValue, mapPropertyDef, parseStoredNoteProperties, tryParseRawNoteProperties, } from '../../vendor/electron/shared/index.js';
|
|
6
|
+
import { fetchDomainReadModel, previewInRolledBackTransaction, runDomainCommand, withNestedWriteTransaction, } from '../domain/run.js';
|
|
7
|
+
function nowIso() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
export async function listProperties(db, input) {
|
|
11
|
+
const rows = await fetchDomainReadModel(db, 'properties.catalog', {
|
|
12
|
+
userId: input.userId,
|
|
13
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
14
|
+
});
|
|
15
|
+
return rows.map(mapPropertyDef);
|
|
16
|
+
}
|
|
17
|
+
/** Resolves a definition by id, falling back to key (keys are unique among active defs). */
|
|
18
|
+
export async function findProperty(db, input) {
|
|
19
|
+
const ref = input.ref.trim();
|
|
20
|
+
const definitions = await listProperties(db, { userId: input.userId, limit: 5000 });
|
|
21
|
+
return definitions.find((definition) => definition.id === ref)
|
|
22
|
+
?? definitions.find((definition) => definition.key === ref)
|
|
23
|
+
?? null;
|
|
24
|
+
}
|
|
25
|
+
export async function requireProperty(db, input) {
|
|
26
|
+
const definition = await findProperty(db, input);
|
|
27
|
+
if (!definition) {
|
|
28
|
+
throw new DinoxError(`Property definition not found: ${input.ref}`, {
|
|
29
|
+
code: 'PROPERTY_DEF_NOT_FOUND',
|
|
30
|
+
details: { ref: input.ref },
|
|
31
|
+
suggestion: 'Run `dino prop list --format json` to see property ids and keys.',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return definition;
|
|
35
|
+
}
|
|
36
|
+
async function runCreate(db, input) {
|
|
37
|
+
const existing = await findProperty(db, { userId: input.userId, ref: input.key });
|
|
38
|
+
const { result: id } = await runDomainCommand(db, 'properties.ensure', {
|
|
39
|
+
key: input.key,
|
|
40
|
+
userId: input.userId,
|
|
41
|
+
updatedAt: nowIso(),
|
|
42
|
+
...(input.name === undefined ? {} : { name: input.name }),
|
|
43
|
+
...(input.type === undefined ? {} : { type: input.type }),
|
|
44
|
+
...(input.optionItems === undefined ? {} : { optionItems: input.optionItems }),
|
|
45
|
+
...(input.uniqueIdPrefix === undefined ? {} : { uniqueIdPrefix: input.uniqueIdPrefix }),
|
|
46
|
+
...(input.config === undefined ? {} : { config: input.config }),
|
|
47
|
+
...(input.description === undefined ? {} : { description: input.description }),
|
|
48
|
+
...(input.icon === undefined ? {} : { icon: input.icon }),
|
|
49
|
+
...(input.source === undefined ? {} : { source: input.source }),
|
|
50
|
+
});
|
|
51
|
+
const property = await requireProperty(db, { userId: input.userId, ref: id });
|
|
52
|
+
return { property, created: existing?.id !== id };
|
|
53
|
+
}
|
|
54
|
+
export async function createProperty(db, input) {
|
|
55
|
+
return runCreate(db, input);
|
|
56
|
+
}
|
|
57
|
+
export async function previewCreateProperty(db, input) {
|
|
58
|
+
return previewInRolledBackTransaction(db, (tx) => runCreate(tx, input), async (_tx, result) => result);
|
|
59
|
+
}
|
|
60
|
+
function resolveOption(definition, ref) {
|
|
61
|
+
const options = definition.options ?? [];
|
|
62
|
+
const option = findPropertyOptionById(options, ref) ?? findPropertyOptionByLabel(options, ref);
|
|
63
|
+
if (!option) {
|
|
64
|
+
throw new DinoxError(`Option not found on property ${definition.key}: ${ref}`, {
|
|
65
|
+
code: 'PROPERTY_OPTION_NOT_FOUND',
|
|
66
|
+
details: { property: definition.key, option: ref },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return option;
|
|
70
|
+
}
|
|
71
|
+
/** Applies add/archive/restore/rename/color edits onto the complete current option list. */
|
|
72
|
+
export function applyOptionEdits(definition, edits) {
|
|
73
|
+
if (definition.optionsMigrationPending) {
|
|
74
|
+
throw new DinoxError('Legacy property options must be migrated first', { code: 'PROPERTY_OPTIONS_MIGRATION_REQUIRED' });
|
|
75
|
+
}
|
|
76
|
+
const next = (definition.options ?? []).map((option) => ({
|
|
77
|
+
id: option.id,
|
|
78
|
+
label: option.label,
|
|
79
|
+
color: option.color,
|
|
80
|
+
sortRank: option.sortRank,
|
|
81
|
+
archived: option.archived,
|
|
82
|
+
...(option.group ? { group: option.group } : {}),
|
|
83
|
+
}));
|
|
84
|
+
const byId = (id) => next.find((option) => option.id === id);
|
|
85
|
+
for (const edit of edits) {
|
|
86
|
+
if (edit.kind === 'add') {
|
|
87
|
+
next.push({ label: edit.label, ...(edit.color ? { color: edit.color } : {}), ...(edit.group ? { group: edit.group } : {}) });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const target = byId(resolveOption(definition, edit.ref).id);
|
|
91
|
+
if (edit.kind === 'archive')
|
|
92
|
+
target.archived = true;
|
|
93
|
+
if (edit.kind === 'restore')
|
|
94
|
+
target.archived = false;
|
|
95
|
+
if (edit.kind === 'rename')
|
|
96
|
+
target.label = edit.label;
|
|
97
|
+
if (edit.kind === 'color')
|
|
98
|
+
target.color = edit.color;
|
|
99
|
+
}
|
|
100
|
+
return next;
|
|
101
|
+
}
|
|
102
|
+
async function runConfigure(db, input) {
|
|
103
|
+
const definition = await requireProperty(db, { userId: input.userId, ref: input.ref });
|
|
104
|
+
const options = input.optionEdits && input.optionEdits.length > 0
|
|
105
|
+
? applyOptionEdits(definition, input.optionEdits)
|
|
106
|
+
: input.options;
|
|
107
|
+
const { result: changed } = await runDomainCommand(db, 'properties.configure', {
|
|
108
|
+
id: definition.id,
|
|
109
|
+
userId: input.userId,
|
|
110
|
+
updatedAt: nowIso(),
|
|
111
|
+
...(input.name === undefined ? {} : { name: input.name }),
|
|
112
|
+
...(input.description === undefined ? {} : { description: input.description }),
|
|
113
|
+
...(input.icon === undefined ? {} : { icon: input.icon }),
|
|
114
|
+
...(input.showOnCard === undefined ? {} : { showOnCard: input.showOnCard }),
|
|
115
|
+
...(input.sortRank === undefined ? {} : { sortRank: input.sortRank }),
|
|
116
|
+
...(options === undefined ? {} : { options }),
|
|
117
|
+
...(input.uniqueIdPrefix === undefined ? {} : { uniqueIdPrefix: input.uniqueIdPrefix }),
|
|
118
|
+
...(input.repairInvalidOptions ? { repairInvalidOptions: true } : {}),
|
|
119
|
+
...(input.config === undefined ? {} : { config: input.config }),
|
|
120
|
+
});
|
|
121
|
+
return { property: await requireProperty(db, { userId: input.userId, ref: definition.id }), changed };
|
|
122
|
+
}
|
|
123
|
+
export async function configureProperty(db, input) {
|
|
124
|
+
return runConfigure(db, input);
|
|
125
|
+
}
|
|
126
|
+
export async function previewConfigureProperty(db, input) {
|
|
127
|
+
const before = await requireProperty(db, { userId: input.userId, ref: input.ref });
|
|
128
|
+
return previewInRolledBackTransaction(db, (tx) => runConfigure(tx, input), async (_tx, result) => ({ before, ...result }));
|
|
129
|
+
}
|
|
130
|
+
async function runDelete(db, input) {
|
|
131
|
+
const definition = await requireProperty(db, input);
|
|
132
|
+
await runDomainCommand(db, 'properties.softDelete', {
|
|
133
|
+
id: definition.id,
|
|
134
|
+
userId: input.userId,
|
|
135
|
+
updatedAt: nowIso(),
|
|
136
|
+
});
|
|
137
|
+
return definition;
|
|
138
|
+
}
|
|
139
|
+
export async function deleteProperty(db, input) {
|
|
140
|
+
return runDelete(db, input);
|
|
141
|
+
}
|
|
142
|
+
export async function previewDeleteProperty(db, input) {
|
|
143
|
+
return previewInRolledBackTransaction(db, (tx) => runDelete(tx, input), async (_tx, definition) => definition);
|
|
144
|
+
}
|
|
145
|
+
async function runMigrate(db, userId) {
|
|
146
|
+
const { result } = await runDomainCommand(db, 'properties.migrateLegacyOptions', {
|
|
147
|
+
userId,
|
|
148
|
+
updatedAt: nowIso(),
|
|
149
|
+
});
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
export function migrationAffectedCount(result) {
|
|
153
|
+
return result.definitionsMigrated + result.notesUpdated + result.savedViewsUpdated;
|
|
154
|
+
}
|
|
155
|
+
export async function previewMigrateLegacyOptions(db, userId) {
|
|
156
|
+
return previewInRolledBackTransaction(db, (tx) => runMigrate(tx, userId), async (_tx, result) => result);
|
|
157
|
+
}
|
|
158
|
+
/** Runs the migration and rolls it back unless it touches exactly `expectedCount` records. */
|
|
159
|
+
export async function migrateLegacyOptions(db, input) {
|
|
160
|
+
return withNestedWriteTransaction(db, async (tx) => {
|
|
161
|
+
const result = await runMigrate(tx, input.userId);
|
|
162
|
+
const actualCount = migrationAffectedCount(result);
|
|
163
|
+
if (actualCount !== input.expectedCount) {
|
|
164
|
+
throw new DinoxError(`--expected-count ${input.expectedCount} does not match current affected record count ${actualCount}`, {
|
|
165
|
+
code: 'PRECONDITION_FAILED',
|
|
166
|
+
details: { expectedCount: input.expectedCount, actualCount, result },
|
|
167
|
+
suggestion: 'Re-run `dino prop migrate-options --dry-run --format json` and retry with changes.affected_count.',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function displayEntryValue(definition, value) {
|
|
174
|
+
if (value === null)
|
|
175
|
+
return null;
|
|
176
|
+
const options = definition?.options;
|
|
177
|
+
if (options && (definition.type === 'select' || definition.type === 'status') && typeof value === 'string') {
|
|
178
|
+
const option = findPropertyOptionById(options, value);
|
|
179
|
+
return option ? { id: option.id, label: option.label, color: option.color, archived: option.archived, ...(option.group ? { group: option.group } : {}) } : { id: value, label: null, unknown: true };
|
|
180
|
+
}
|
|
181
|
+
if (options && definition.type === 'multi_select' && Array.isArray(value)) {
|
|
182
|
+
return value.map((item) => {
|
|
183
|
+
const option = findPropertyOptionById(options, item);
|
|
184
|
+
return option ? { id: option.id, label: option.label, color: option.color, archived: option.archived } : { id: item, label: null, unknown: true };
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (definition?.type === 'unique_id' && typeof value === 'number' && definition.uniqueId) {
|
|
188
|
+
return `${definition.uniqueId.prefix}-${value}`;
|
|
189
|
+
}
|
|
190
|
+
return formatPropertyValue(value);
|
|
191
|
+
}
|
|
192
|
+
export async function readNoteProperties(db, input) {
|
|
193
|
+
const row = await db.getOptional(`
|
|
194
|
+
SELECT id, properties
|
|
195
|
+
FROM c_note
|
|
196
|
+
WHERE id = ? AND user_no = ? AND (is_del IS NULL OR is_del = 0)
|
|
197
|
+
LIMIT 1
|
|
198
|
+
`, [input.noteId, input.userId]);
|
|
199
|
+
if (!row) {
|
|
200
|
+
throw new DinoxError(`Note not found: ${input.noteId}`, { code: 'NOTE_NOT_FOUND', details: { noteId: input.noteId } });
|
|
201
|
+
}
|
|
202
|
+
const definitions = new Map((await listProperties(db, { userId: input.userId, limit: 5000 })).map((definition) => [definition.key, definition]));
|
|
203
|
+
const properties = parseStoredNoteProperties(row.properties);
|
|
204
|
+
const entries = Object.entries(properties).map(([key, value]) => {
|
|
205
|
+
const definition = definitions.get(key);
|
|
206
|
+
return {
|
|
207
|
+
key,
|
|
208
|
+
name: definition?.name || key,
|
|
209
|
+
type: definition?.type ?? (value === null ? 'text' : inferPropertyTypeFromValue(value)),
|
|
210
|
+
defined: Boolean(definition),
|
|
211
|
+
propertyDefinitionId: definition?.id ?? null,
|
|
212
|
+
value,
|
|
213
|
+
display: displayEntryValue(definition, value),
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
return {
|
|
217
|
+
noteId: row.id,
|
|
218
|
+
valid: tryParseRawNoteProperties(row.properties) !== null,
|
|
219
|
+
properties,
|
|
220
|
+
entries,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async function mutate(db, input, run, dryRun) {
|
|
224
|
+
const before = await readNoteProperties(db, input);
|
|
225
|
+
const execute = async (tx) => {
|
|
226
|
+
const result = await run(tx);
|
|
227
|
+
const after = await readNoteProperties(tx, input);
|
|
228
|
+
return { result, after };
|
|
229
|
+
};
|
|
230
|
+
const { result, after } = dryRun
|
|
231
|
+
? await previewInRolledBackTransaction(db, execute, async (_tx, value) => value)
|
|
232
|
+
: await execute(db);
|
|
233
|
+
return {
|
|
234
|
+
result,
|
|
235
|
+
changed: JSON.stringify(before.properties) !== JSON.stringify(after.properties),
|
|
236
|
+
before,
|
|
237
|
+
after,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
export async function setNoteProperties(db, input) {
|
|
241
|
+
return mutate(db, input, (tx) => runDomainCommand(tx, 'notes.updateProperties', {
|
|
242
|
+
noteId: input.noteId,
|
|
243
|
+
userId: input.userId,
|
|
244
|
+
patch: input.patch,
|
|
245
|
+
updatedAt: nowIso(),
|
|
246
|
+
}), Boolean(input.dryRun));
|
|
247
|
+
}
|
|
248
|
+
export async function fillEmptyNoteProperties(db, input) {
|
|
249
|
+
return mutate(db, input, async (tx) => (await runDomainCommand(tx, 'notes.fillEmptyProperties', {
|
|
250
|
+
noteId: input.noteId,
|
|
251
|
+
userId: input.userId,
|
|
252
|
+
values: input.values,
|
|
253
|
+
updatedAt: nowIso(),
|
|
254
|
+
})).result, Boolean(input.dryRun));
|
|
255
|
+
}
|
|
256
|
+
export async function updateNotePropertySlots(db, input) {
|
|
257
|
+
return mutate(db, input, (tx) => runDomainCommand(tx, 'notes.updatePropertySlots', {
|
|
258
|
+
noteId: input.noteId,
|
|
259
|
+
userId: input.userId,
|
|
260
|
+
operation: input.operation,
|
|
261
|
+
keys: input.keys,
|
|
262
|
+
updatedAt: nowIso(),
|
|
263
|
+
}), Boolean(input.dryRun));
|
|
264
|
+
}
|
|
265
|
+
export async function reorderNotePropertySlots(db, input) {
|
|
266
|
+
return mutate(db, input, (tx) => runDomainCommand(tx, 'notes.reorderPropertySlots', {
|
|
267
|
+
noteId: input.noteId,
|
|
268
|
+
userId: input.userId,
|
|
269
|
+
orderedKeys: input.orderedKeys,
|
|
270
|
+
updatedAt: nowIso(),
|
|
271
|
+
}), Boolean(input.dryRun));
|
|
272
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
+
import { type PropertyDef, type PropertyDefConfig, type PropertyOption, type PropertyType, type SavedViewFieldRef } from '../../vendor/electron/shared/index.js';
|
|
3
|
+
export type SavedViewFieldValueType = 'text' | 'select' | 'list' | 'number' | 'date' | 'boolean';
|
|
4
|
+
export type SavedViewFieldCatalogItem = {
|
|
5
|
+
field: SavedViewFieldRef;
|
|
6
|
+
name: string;
|
|
7
|
+
/** How filter values are compared: select/list values are option ids (or note/box ids). */
|
|
8
|
+
type: SavedViewFieldValueType;
|
|
9
|
+
propertyType: PropertyType;
|
|
10
|
+
sortable: boolean;
|
|
11
|
+
filterable: boolean;
|
|
12
|
+
displayOnly: boolean;
|
|
13
|
+
filterOperators: string[];
|
|
14
|
+
available: boolean;
|
|
15
|
+
error?: {
|
|
16
|
+
code: string;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
options?: readonly PropertyOption[];
|
|
20
|
+
values?: readonly string[];
|
|
21
|
+
config?: PropertyDefConfig;
|
|
22
|
+
propertyDefinitionId?: string;
|
|
23
|
+
description?: string | null;
|
|
24
|
+
icon?: string | null;
|
|
25
|
+
};
|
|
26
|
+
export declare function valueTypeForPropertyType(type: PropertyType): SavedViewFieldValueType;
|
|
27
|
+
export declare function listPropertyDefinitions(db: AbstractPowerSyncDatabase, userId: string, limit?: number): Promise<PropertyDef[]>;
|
|
28
|
+
export declare function listSavedViewFields(db: AbstractPowerSyncDatabase, userId: string): Promise<SavedViewFieldCatalogItem[]>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Field catalog for saved views: system fields plus the account's property definitions,
|
|
2
|
+
// each with the operators and sortability the desktop editor offers for it.
|
|
3
|
+
import { canSortSavedViewField, getSavedViewFilterOperatorsForField, isSavedViewDisplayOnlyField, mapPropertyDef, SAVED_VIEW_PRIMARY_RESOURCE_TYPES, SAVED_VIEW_SYSTEM_FIELDS, } from '../../vendor/electron/shared/index.js';
|
|
4
|
+
import { fetchDomainReadModel } from '../domain/run.js';
|
|
5
|
+
const SYSTEM_FIELD_META = {
|
|
6
|
+
'sys.title': { name: 'Title', propertyType: 'text', type: 'text' },
|
|
7
|
+
'sys.created_at': { name: 'Created at', propertyType: 'date', type: 'date' },
|
|
8
|
+
'sys.updated_at': { name: 'Updated at', propertyType: 'date', type: 'date' },
|
|
9
|
+
'sys.tags': { name: 'Tags', propertyType: 'multi_select', type: 'list' },
|
|
10
|
+
'sys.zettel_boxes': { name: 'Zettel boxes', propertyType: 'relation', type: 'list' },
|
|
11
|
+
'sys.source': { name: 'Source', propertyType: 'text', type: 'text' },
|
|
12
|
+
'sys.type': { name: 'Type', propertyType: 'text', type: 'text' },
|
|
13
|
+
'sys.primary_resource_type': { name: 'Primary resource type', propertyType: 'select', type: 'select' },
|
|
14
|
+
'sys.starred_at': { name: 'Starred at', propertyType: 'date', type: 'date' },
|
|
15
|
+
'sys.images': { name: 'Images', propertyType: 'text', type: 'text' },
|
|
16
|
+
'sys.cover': { name: 'Cover', propertyType: 'text', type: 'text' },
|
|
17
|
+
'sys.audio': { name: 'Audio', propertyType: 'text', type: 'text' },
|
|
18
|
+
'sys.video': { name: 'Video', propertyType: 'text', type: 'text' },
|
|
19
|
+
'sys.file': { name: 'File', propertyType: 'text', type: 'text' },
|
|
20
|
+
};
|
|
21
|
+
export function valueTypeForPropertyType(type) {
|
|
22
|
+
switch (type) {
|
|
23
|
+
case 'number':
|
|
24
|
+
case 'unique_id':
|
|
25
|
+
return 'number';
|
|
26
|
+
case 'date':
|
|
27
|
+
return 'date';
|
|
28
|
+
case 'select':
|
|
29
|
+
case 'status':
|
|
30
|
+
return 'select';
|
|
31
|
+
case 'checkbox':
|
|
32
|
+
return 'boolean';
|
|
33
|
+
case 'multi_select':
|
|
34
|
+
case 'relation':
|
|
35
|
+
return 'list';
|
|
36
|
+
default:
|
|
37
|
+
return 'text';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function catalogItem(field, name, propertyType, type) {
|
|
41
|
+
const displayOnly = isSavedViewDisplayOnlyField(field);
|
|
42
|
+
const filterOperators = getSavedViewFilterOperatorsForField(field, propertyType);
|
|
43
|
+
return {
|
|
44
|
+
field,
|
|
45
|
+
name,
|
|
46
|
+
type,
|
|
47
|
+
propertyType,
|
|
48
|
+
sortable: canSortSavedViewField(field, propertyType),
|
|
49
|
+
filterable: filterOperators.length > 0,
|
|
50
|
+
displayOnly,
|
|
51
|
+
filterOperators,
|
|
52
|
+
available: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function propertyItem(definition) {
|
|
56
|
+
const field = `prop.${definition.key}`;
|
|
57
|
+
const item = {
|
|
58
|
+
...catalogItem(field, definition.name || definition.key, definition.type, valueTypeForPropertyType(definition.type)),
|
|
59
|
+
propertyDefinitionId: definition.id,
|
|
60
|
+
description: definition.description ?? null,
|
|
61
|
+
icon: definition.icon ?? null,
|
|
62
|
+
...(definition.options ? { options: definition.options } : {}),
|
|
63
|
+
...(definition.config ? { config: definition.config } : {}),
|
|
64
|
+
};
|
|
65
|
+
const error = definition.optionsMigrationPending
|
|
66
|
+
? { code: 'PROPERTY_OPTIONS_MIGRATION_REQUIRED', message: 'Legacy select options must be migrated first (dino prop migrate-options)' }
|
|
67
|
+
: definition.optionsInvalid
|
|
68
|
+
? { code: 'PROPERTY_OPTIONS_INVALID', message: 'Stored property options are invalid' }
|
|
69
|
+
: definition.configInvalid
|
|
70
|
+
? { code: 'PROPERTY_DEF_CONFIG_INVALID', message: 'Stored property config is invalid' }
|
|
71
|
+
: null;
|
|
72
|
+
return error ? { ...item, available: false, error } : item;
|
|
73
|
+
}
|
|
74
|
+
export async function listPropertyDefinitions(db, userId, limit) {
|
|
75
|
+
const rows = await fetchDomainReadModel(db, 'properties.catalog', { userId, ...(limit ? { limit } : {}) });
|
|
76
|
+
return rows.map(mapPropertyDef);
|
|
77
|
+
}
|
|
78
|
+
export async function listSavedViewFields(db, userId) {
|
|
79
|
+
const systemItems = SAVED_VIEW_SYSTEM_FIELDS.map((name) => {
|
|
80
|
+
const field = `sys.${name}`;
|
|
81
|
+
const meta = SYSTEM_FIELD_META[field] ?? { name, propertyType: 'text', type: 'text' };
|
|
82
|
+
const item = catalogItem(field, meta.name, meta.propertyType, meta.type);
|
|
83
|
+
return field === 'sys.primary_resource_type' ? { ...item, values: SAVED_VIEW_PRIMARY_RESOURCE_TYPES } : item;
|
|
84
|
+
});
|
|
85
|
+
const definitions = await listPropertyDefinitions(db, userId);
|
|
86
|
+
return [...systemItems, ...definitions.map(propertyItem)];
|
|
87
|
+
}
|