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/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
+ });