trilium-api 1.0.1 → 1.0.4
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 +173 -59
- package/dist/index.cjs +386 -0
- package/{src/generated/trilium.d.ts → dist/index.d.cts} +472 -5
- package/dist/index.d.ts +2225 -0
- package/dist/index.js +344 -0
- package/package.json +36 -5
- package/.github/workflows/ci.yml +0 -37
- package/.github/workflows/publish.yml +0 -84
- package/src/client.test.ts +0 -477
- package/src/client.ts +0 -91
- package/src/demo-mapper.ts +0 -166
- package/src/demo-search.ts +0 -108
- package/src/demo.ts +0 -126
- package/src/index.ts +0 -35
- package/src/mapper.test.ts +0 -638
- package/src/mapper.ts +0 -534
- package/tsconfig.json +0 -42
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
StandardNoteMapping: () => StandardNoteMapping,
|
|
34
|
+
TriliumMapper: () => TriliumMapper,
|
|
35
|
+
buildSearchQuery: () => buildSearchQuery,
|
|
36
|
+
createClient: () => client_default,
|
|
37
|
+
createTriliumClient: () => createTriliumClient,
|
|
38
|
+
transforms: () => transforms
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/client.ts
|
|
43
|
+
var import_openapi_fetch = __toESM(require("openapi-fetch"), 1);
|
|
44
|
+
|
|
45
|
+
// src/mapper.ts
|
|
46
|
+
function buildSearchQuery(helpers) {
|
|
47
|
+
if ("AND" in helpers && Array.isArray(helpers.AND)) {
|
|
48
|
+
return helpers.AND.map((h) => {
|
|
49
|
+
const query = buildSearchQuery(h);
|
|
50
|
+
return query.includes(" OR ") ? `(${query})` : query;
|
|
51
|
+
}).join(" AND ");
|
|
52
|
+
}
|
|
53
|
+
if ("OR" in helpers && Array.isArray(helpers.OR)) {
|
|
54
|
+
return helpers.OR.map((h) => {
|
|
55
|
+
const query = buildSearchQuery(h);
|
|
56
|
+
return query.includes(" AND ") || query.includes(" OR ") ? `(${query})` : query;
|
|
57
|
+
}).join(" OR ");
|
|
58
|
+
}
|
|
59
|
+
if ("NOT" in helpers && helpers.NOT !== void 0) {
|
|
60
|
+
const notValue = helpers.NOT;
|
|
61
|
+
if (typeof notValue === "object" && notValue !== null && !("value" in notValue)) {
|
|
62
|
+
const query = buildSearchQuery(notValue);
|
|
63
|
+
return `not(${query})`;
|
|
64
|
+
}
|
|
65
|
+
throw new Error("NOT operator requires a query object, not a simple value");
|
|
66
|
+
}
|
|
67
|
+
const parts = [];
|
|
68
|
+
for (const [key, value] of Object.entries(helpers)) {
|
|
69
|
+
if (value === void 0 || value === null) continue;
|
|
70
|
+
if (key.startsWith("#")) {
|
|
71
|
+
const labelName = key.slice(1);
|
|
72
|
+
if (labelName.includes(".")) {
|
|
73
|
+
if (typeof value === "object" && "value" in value) {
|
|
74
|
+
const operator = value.operator || "=";
|
|
75
|
+
const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
|
|
76
|
+
parts.push(`${key} ${operator} ${val}`);
|
|
77
|
+
} else if (typeof value === "string") {
|
|
78
|
+
parts.push(`${key} = '${value}'`);
|
|
79
|
+
} else {
|
|
80
|
+
parts.push(`${key} = ${value}`);
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
if (value === true) {
|
|
84
|
+
parts.push(`#${labelName}`);
|
|
85
|
+
} else if (value === false) {
|
|
86
|
+
parts.push(`#!${labelName}`);
|
|
87
|
+
} else if (typeof value === "object" && "value" in value) {
|
|
88
|
+
const operator = value.operator || "=";
|
|
89
|
+
const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
|
|
90
|
+
parts.push(`#${labelName} ${operator} ${val}`);
|
|
91
|
+
} else if (typeof value === "string") {
|
|
92
|
+
parts.push(`#${labelName} = '${value}'`);
|
|
93
|
+
} else {
|
|
94
|
+
parts.push(`#${labelName} = ${value}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else if (key.startsWith("~")) {
|
|
98
|
+
const relationName = key.slice(1);
|
|
99
|
+
if (relationName.includes(".")) {
|
|
100
|
+
if (typeof value === "object" && "value" in value) {
|
|
101
|
+
const operator = value.operator || "=";
|
|
102
|
+
const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
|
|
103
|
+
parts.push(`${key} ${operator} ${val}`);
|
|
104
|
+
} else if (typeof value === "string") {
|
|
105
|
+
parts.push(`${key} = '${value}'`);
|
|
106
|
+
} else {
|
|
107
|
+
parts.push(`${key} = ${value}`);
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
if (typeof value === "object" && "value" in value) {
|
|
111
|
+
const operator = value.operator || "*=*";
|
|
112
|
+
const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
|
|
113
|
+
parts.push(`${key} ${operator} ${val}`);
|
|
114
|
+
} else if (typeof value === "string") {
|
|
115
|
+
parts.push(`${key} *=* '${value}'`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
const path = key.startsWith("note.") ? key : `note.${key}`;
|
|
120
|
+
if (typeof value === "object" && "value" in value) {
|
|
121
|
+
const operator = value.operator || "=";
|
|
122
|
+
const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
|
|
123
|
+
parts.push(`${path} ${operator} ${val}`);
|
|
124
|
+
} else if (typeof value === "string") {
|
|
125
|
+
parts.push(`${path} = '${value}'`);
|
|
126
|
+
} else if (typeof value === "boolean") {
|
|
127
|
+
parts.push(`${path} = ${value}`);
|
|
128
|
+
} else {
|
|
129
|
+
parts.push(`${path} = ${value}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return parts.join(" AND ");
|
|
134
|
+
}
|
|
135
|
+
var TriliumMapper = class {
|
|
136
|
+
/** The mapping configuration for this mapper */
|
|
137
|
+
config;
|
|
138
|
+
/**
|
|
139
|
+
* Creates a new TriliumMapper instance
|
|
140
|
+
* @param config - The mapping configuration defining how to map note fields to the target type
|
|
141
|
+
*/
|
|
142
|
+
constructor(config) {
|
|
143
|
+
this.config = config;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Merges multiple mapping configurations into a single configuration
|
|
147
|
+
* Later configs override earlier ones for the same keys
|
|
148
|
+
* Supports merging configs from base types into derived types
|
|
149
|
+
*
|
|
150
|
+
* @template T - The target type for the merged configuration
|
|
151
|
+
* @param configs - One or more mapping configurations to merge
|
|
152
|
+
* @returns A new merged mapping configuration
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* const merged = TriliumMapper.merge<BlogPost>(
|
|
156
|
+
* StandardNoteMapping,
|
|
157
|
+
* BlogSpecificMapping,
|
|
158
|
+
* OverrideMapping
|
|
159
|
+
* );
|
|
160
|
+
*/
|
|
161
|
+
static merge(...configs) {
|
|
162
|
+
return Object.assign({}, ...configs);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Maps one or more Trilium notes to the target type
|
|
166
|
+
* @param noteOrNotes - A single note or array of notes to map
|
|
167
|
+
* @returns A single mapped object or array of mapped objects
|
|
168
|
+
*/
|
|
169
|
+
map(noteOrNotes) {
|
|
170
|
+
return Array.isArray(noteOrNotes) ? noteOrNotes.map((note) => this.mapSingle(note)) : this.mapSingle(noteOrNotes);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Maps a single note to the target type using the configured field mappings
|
|
174
|
+
* Processes in two passes: first regular fields, then computed fields
|
|
175
|
+
* @param note - The Trilium note to map
|
|
176
|
+
* @returns The mapped object
|
|
177
|
+
* @throws Error if a required field is missing
|
|
178
|
+
* @private
|
|
179
|
+
*/
|
|
180
|
+
mapSingle(note) {
|
|
181
|
+
const result = {};
|
|
182
|
+
const computedFields = [];
|
|
183
|
+
for (const [key, fieldMapping] of Object.entries(this.config)) {
|
|
184
|
+
if (!fieldMapping) continue;
|
|
185
|
+
if (typeof fieldMapping === "object" && "computed" in fieldMapping) {
|
|
186
|
+
computedFields.push([key, fieldMapping]);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const mapping = typeof fieldMapping === "string" ? { from: fieldMapping } : fieldMapping;
|
|
190
|
+
let value = typeof mapping.from === "function" ? mapping.from(note) : this.extractValue(note, mapping.from);
|
|
191
|
+
if (mapping.transform) {
|
|
192
|
+
value = mapping.transform(value, note);
|
|
193
|
+
}
|
|
194
|
+
if (value === void 0 && mapping.default !== void 0) {
|
|
195
|
+
value = mapping.default;
|
|
196
|
+
}
|
|
197
|
+
if (mapping.required && value === void 0) {
|
|
198
|
+
throw new Error(`Required field '${String(key)}' missing from note ${note.noteId} (${note.title})`);
|
|
199
|
+
}
|
|
200
|
+
result[key] = value;
|
|
201
|
+
}
|
|
202
|
+
for (const [key, mapping] of computedFields) {
|
|
203
|
+
let value = mapping.computed(result, note);
|
|
204
|
+
if (value === void 0 && mapping.default !== void 0) {
|
|
205
|
+
value = mapping.default;
|
|
206
|
+
}
|
|
207
|
+
result[key] = value;
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Extracts a value from a note using a string path
|
|
213
|
+
*
|
|
214
|
+
* Supports:
|
|
215
|
+
* - Label attributes: #labelName
|
|
216
|
+
* - Relation attributes: ~relationName
|
|
217
|
+
* - Note properties: note.property.path
|
|
218
|
+
*
|
|
219
|
+
* @param note - The Trilium note to extract from
|
|
220
|
+
* @param path - The path string indicating where to extract the value
|
|
221
|
+
* @returns The extracted value or undefined if not found
|
|
222
|
+
* @private
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* extractValue(note, 'note.title') // => note.title
|
|
226
|
+
* extractValue(note, '#slug') // => label attribute 'slug'
|
|
227
|
+
* extractValue(note, '~template') // => relation attribute 'template'
|
|
228
|
+
*/
|
|
229
|
+
extractValue(note, path) {
|
|
230
|
+
if (!path) return void 0;
|
|
231
|
+
if (path.startsWith("#")) {
|
|
232
|
+
return note.attributes?.find((attr) => attr.type === "label" && attr.name === path.slice(1))?.value;
|
|
233
|
+
}
|
|
234
|
+
if (path.startsWith("~")) {
|
|
235
|
+
return note.attributes?.find((attr) => attr.type === "relation" && attr.name === path.slice(1))?.value;
|
|
236
|
+
}
|
|
237
|
+
if (path.startsWith("note.")) {
|
|
238
|
+
return path.slice(5).split(".").reduce((obj, key) => obj?.[key], note);
|
|
239
|
+
}
|
|
240
|
+
return void 0;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
var transforms = {
|
|
244
|
+
/** Convert to number */
|
|
245
|
+
number: (value) => {
|
|
246
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
247
|
+
const num = Number(value);
|
|
248
|
+
return isNaN(num) ? void 0 : num;
|
|
249
|
+
},
|
|
250
|
+
/** Convert to boolean */
|
|
251
|
+
boolean: (value) => {
|
|
252
|
+
if (value === void 0 || value === null) return void 0;
|
|
253
|
+
if (typeof value === "boolean") return value;
|
|
254
|
+
if (typeof value === "string") {
|
|
255
|
+
const lower = value.toLowerCase();
|
|
256
|
+
if (lower === "true" || lower === "1" || lower === "yes") return true;
|
|
257
|
+
if (lower === "false" || lower === "0" || lower === "no") return false;
|
|
258
|
+
}
|
|
259
|
+
return void 0;
|
|
260
|
+
},
|
|
261
|
+
/** Split comma-separated string into array */
|
|
262
|
+
commaSeparated: (value) => {
|
|
263
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
264
|
+
if (typeof value !== "string") return void 0;
|
|
265
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
266
|
+
},
|
|
267
|
+
/** Parse JSON string */
|
|
268
|
+
json: (value) => {
|
|
269
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
270
|
+
if (typeof value !== "string") return void 0;
|
|
271
|
+
try {
|
|
272
|
+
return JSON.parse(value);
|
|
273
|
+
} catch {
|
|
274
|
+
return void 0;
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
/** Parse date string */
|
|
278
|
+
date: (value) => {
|
|
279
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
280
|
+
const date = new Date(String(value));
|
|
281
|
+
return isNaN(date.getTime()) ? void 0 : date;
|
|
282
|
+
},
|
|
283
|
+
/** Trim whitespace from string */
|
|
284
|
+
trim: (value) => {
|
|
285
|
+
if (value === void 0 || value === null) return void 0;
|
|
286
|
+
return String(value).trim() || void 0;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
var StandardNoteMapping = {
|
|
290
|
+
id: {
|
|
291
|
+
from: "note.noteId",
|
|
292
|
+
required: true
|
|
293
|
+
},
|
|
294
|
+
title: {
|
|
295
|
+
from: "note.title",
|
|
296
|
+
required: true
|
|
297
|
+
},
|
|
298
|
+
dateCreatedUtc: {
|
|
299
|
+
from: "note.utcDateCreated",
|
|
300
|
+
transform: transforms.date,
|
|
301
|
+
required: true
|
|
302
|
+
},
|
|
303
|
+
dateLastModifiedUtc: {
|
|
304
|
+
from: "note.utcDateModified",
|
|
305
|
+
transform: transforms.date,
|
|
306
|
+
required: true
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// src/client.ts
|
|
311
|
+
function createTriliumClient(config) {
|
|
312
|
+
const baseUrl = config.baseUrl.endsWith("/") ? config.baseUrl.slice(0, -1) : config.baseUrl;
|
|
313
|
+
const client = (0, import_openapi_fetch.default)({
|
|
314
|
+
baseUrl: `${baseUrl}/etapi`,
|
|
315
|
+
headers: {
|
|
316
|
+
Authorization: config.apiKey
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
const searchAndMap = async (options) => {
|
|
320
|
+
const searchQuery = typeof options.query === "string" ? options.query : buildSearchQuery(options.query);
|
|
321
|
+
const params = [];
|
|
322
|
+
if (options.orderBy) {
|
|
323
|
+
params.push(`orderBy:${options.orderBy}`);
|
|
324
|
+
if (options.orderDirection) {
|
|
325
|
+
params.push(options.orderDirection);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (options.limit) {
|
|
329
|
+
params.push(`limit:${options.limit}`);
|
|
330
|
+
}
|
|
331
|
+
if (options.fastSearch) {
|
|
332
|
+
params.push("fastSearch");
|
|
333
|
+
}
|
|
334
|
+
const fullQuery = params.length > 0 ? `${searchQuery} ${params.join(" ")}` : searchQuery;
|
|
335
|
+
const { data, error } = await client.GET("/notes", {
|
|
336
|
+
params: { query: { search: fullQuery } }
|
|
337
|
+
});
|
|
338
|
+
if (error) {
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
if (!data?.results) {
|
|
342
|
+
throw new Error("No results returned from search");
|
|
343
|
+
}
|
|
344
|
+
const fullMapping = TriliumMapper.merge(
|
|
345
|
+
StandardNoteMapping,
|
|
346
|
+
options.mapping
|
|
347
|
+
);
|
|
348
|
+
const mapper = new TriliumMapper(fullMapping);
|
|
349
|
+
const mappedData = [];
|
|
350
|
+
const failures = [];
|
|
351
|
+
for (const note of data.results) {
|
|
352
|
+
try {
|
|
353
|
+
const [mapped] = mapper.map([note]);
|
|
354
|
+
if (mapped !== void 0) {
|
|
355
|
+
mappedData.push(mapped);
|
|
356
|
+
} else {
|
|
357
|
+
failures.push({
|
|
358
|
+
noteId: note.noteId ?? "unknown",
|
|
359
|
+
noteTitle: note.title ?? "Untitled",
|
|
360
|
+
reason: "Mapping returned undefined",
|
|
361
|
+
note
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
} catch (err) {
|
|
365
|
+
failures.push({
|
|
366
|
+
noteId: note.noteId ?? "unknown",
|
|
367
|
+
noteTitle: note.title ?? "Untitled",
|
|
368
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
369
|
+
note
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return { data: mappedData, failures };
|
|
374
|
+
};
|
|
375
|
+
return Object.assign(client, { searchAndMap });
|
|
376
|
+
}
|
|
377
|
+
var client_default = createTriliumClient;
|
|
378
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
379
|
+
0 && (module.exports = {
|
|
380
|
+
StandardNoteMapping,
|
|
381
|
+
TriliumMapper,
|
|
382
|
+
buildSearchQuery,
|
|
383
|
+
createClient,
|
|
384
|
+
createTriliumClient,
|
|
385
|
+
transforms
|
|
386
|
+
});
|