trilium-api 1.0.0 → 1.0.2

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.js ADDED
@@ -0,0 +1,266 @@
1
+ // src/client.ts
2
+ import createClient from "openapi-fetch";
3
+
4
+ // src/mapper.ts
5
+ function buildSearchQuery(helpers) {
6
+ if ("AND" in helpers && Array.isArray(helpers.AND)) {
7
+ return helpers.AND.map((h) => {
8
+ const query = buildSearchQuery(h);
9
+ return query.includes(" OR ") ? `(${query})` : query;
10
+ }).join(" AND ");
11
+ }
12
+ if ("OR" in helpers && Array.isArray(helpers.OR)) {
13
+ return helpers.OR.map((h) => {
14
+ const query = buildSearchQuery(h);
15
+ return query.includes(" AND ") || query.includes(" OR ") ? `(${query})` : query;
16
+ }).join(" OR ");
17
+ }
18
+ if ("NOT" in helpers && helpers.NOT !== void 0) {
19
+ const notValue = helpers.NOT;
20
+ if (typeof notValue === "object" && notValue !== null && !("value" in notValue)) {
21
+ const query = buildSearchQuery(notValue);
22
+ return `not(${query})`;
23
+ }
24
+ throw new Error("NOT operator requires a query object, not a simple value");
25
+ }
26
+ const parts = [];
27
+ for (const [key, value] of Object.entries(helpers)) {
28
+ if (value === void 0 || value === null) continue;
29
+ if (key.startsWith("#")) {
30
+ const labelName = key.slice(1);
31
+ if (labelName.includes(".")) {
32
+ if (typeof value === "object" && "value" in value) {
33
+ const operator = value.operator || "=";
34
+ const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
35
+ parts.push(`${key} ${operator} ${val}`);
36
+ } else if (typeof value === "string") {
37
+ parts.push(`${key} = '${value}'`);
38
+ } else {
39
+ parts.push(`${key} = ${value}`);
40
+ }
41
+ } else {
42
+ if (value === true) {
43
+ parts.push(`#${labelName}`);
44
+ } else if (value === false) {
45
+ parts.push(`#!${labelName}`);
46
+ } else if (typeof value === "object" && "value" in value) {
47
+ const operator = value.operator || "=";
48
+ const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
49
+ parts.push(`#${labelName} ${operator} ${val}`);
50
+ } else if (typeof value === "string") {
51
+ parts.push(`#${labelName} = '${value}'`);
52
+ } else {
53
+ parts.push(`#${labelName} = ${value}`);
54
+ }
55
+ }
56
+ } else if (key.startsWith("~")) {
57
+ const relationName = key.slice(1);
58
+ if (relationName.includes(".")) {
59
+ if (typeof value === "object" && "value" in value) {
60
+ const operator = value.operator || "=";
61
+ const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
62
+ parts.push(`${key} ${operator} ${val}`);
63
+ } else if (typeof value === "string") {
64
+ parts.push(`${key} = '${value}'`);
65
+ } else {
66
+ parts.push(`${key} = ${value}`);
67
+ }
68
+ } else {
69
+ if (typeof value === "object" && "value" in value) {
70
+ const operator = value.operator || "*=*";
71
+ const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
72
+ parts.push(`${key} ${operator} ${val}`);
73
+ } else if (typeof value === "string") {
74
+ parts.push(`${key} *=* '${value}'`);
75
+ }
76
+ }
77
+ } else {
78
+ const path = key.startsWith("note.") ? key : `note.${key}`;
79
+ if (typeof value === "object" && "value" in value) {
80
+ const operator = value.operator || "=";
81
+ const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
82
+ parts.push(`${path} ${operator} ${val}`);
83
+ } else if (typeof value === "string") {
84
+ parts.push(`${path} = '${value}'`);
85
+ } else if (typeof value === "boolean") {
86
+ parts.push(`${path} = ${value}`);
87
+ } else {
88
+ parts.push(`${path} = ${value}`);
89
+ }
90
+ }
91
+ }
92
+ return parts.join(" AND ");
93
+ }
94
+ var TriliumMapper = class {
95
+ /** The mapping configuration for this mapper */
96
+ config;
97
+ /**
98
+ * Creates a new TriliumMapper instance
99
+ * @param config - The mapping configuration defining how to map note fields to the target type
100
+ */
101
+ constructor(config) {
102
+ this.config = config;
103
+ }
104
+ /**
105
+ * Merges multiple mapping configurations into a single configuration
106
+ * Later configs override earlier ones for the same keys
107
+ * Supports merging configs from base types into derived types
108
+ *
109
+ * @template T - The target type for the merged configuration
110
+ * @param configs - One or more mapping configurations to merge
111
+ * @returns A new merged mapping configuration
112
+ *
113
+ * @example
114
+ * const merged = TriliumMapper.merge<BlogPost>(
115
+ * StandardNoteMapping,
116
+ * BlogSpecificMapping,
117
+ * OverrideMapping
118
+ * );
119
+ */
120
+ static merge(...configs) {
121
+ return Object.assign({}, ...configs);
122
+ }
123
+ /**
124
+ * Maps one or more Trilium notes to the target type
125
+ * @param noteOrNotes - A single note or array of notes to map
126
+ * @returns A single mapped object or array of mapped objects
127
+ */
128
+ map(noteOrNotes) {
129
+ return Array.isArray(noteOrNotes) ? noteOrNotes.map((note) => this.mapSingle(note)) : this.mapSingle(noteOrNotes);
130
+ }
131
+ /**
132
+ * Maps a single note to the target type using the configured field mappings
133
+ * Processes in two passes: first regular fields, then computed fields
134
+ * @param note - The Trilium note to map
135
+ * @returns The mapped object
136
+ * @throws Error if a required field is missing
137
+ * @private
138
+ */
139
+ mapSingle(note) {
140
+ const result = {};
141
+ const computedFields = [];
142
+ for (const [key, fieldMapping] of Object.entries(this.config)) {
143
+ if (!fieldMapping) continue;
144
+ if (typeof fieldMapping === "object" && "computed" in fieldMapping) {
145
+ computedFields.push([key, fieldMapping]);
146
+ continue;
147
+ }
148
+ const mapping = typeof fieldMapping === "string" ? { from: fieldMapping } : fieldMapping;
149
+ let value = typeof mapping.from === "function" ? mapping.from(note) : this.extractValue(note, mapping.from);
150
+ if (mapping.transform) {
151
+ value = mapping.transform(value, note);
152
+ }
153
+ if (value === void 0 && mapping.default !== void 0) {
154
+ value = mapping.default;
155
+ }
156
+ if (mapping.required && value === void 0) {
157
+ throw new Error(`Required field '${String(key)}' missing from note ${note.noteId} (${note.title})`);
158
+ }
159
+ result[key] = value;
160
+ }
161
+ for (const [key, mapping] of computedFields) {
162
+ let value = mapping.computed(result, note);
163
+ if (value === void 0 && mapping.default !== void 0) {
164
+ value = mapping.default;
165
+ }
166
+ result[key] = value;
167
+ }
168
+ return result;
169
+ }
170
+ /**
171
+ * Extracts a value from a note using a string path
172
+ *
173
+ * Supports:
174
+ * - Label attributes: #labelName
175
+ * - Relation attributes: ~relationName
176
+ * - Note properties: note.property.path
177
+ *
178
+ * @param note - The Trilium note to extract from
179
+ * @param path - The path string indicating where to extract the value
180
+ * @returns The extracted value or undefined if not found
181
+ * @private
182
+ *
183
+ * @example
184
+ * extractValue(note, 'note.title') // => note.title
185
+ * extractValue(note, '#slug') // => label attribute 'slug'
186
+ * extractValue(note, '~template') // => relation attribute 'template'
187
+ */
188
+ extractValue(note, path) {
189
+ if (!path) return void 0;
190
+ if (path.startsWith("#")) {
191
+ return note.attributes?.find((attr) => attr.type === "label" && attr.name === path.slice(1))?.value;
192
+ }
193
+ if (path.startsWith("~")) {
194
+ return note.attributes?.find((attr) => attr.type === "relation" && attr.name === path.slice(1))?.value;
195
+ }
196
+ if (path.startsWith("note.")) {
197
+ return path.slice(5).split(".").reduce((obj, key) => obj?.[key], note);
198
+ }
199
+ return void 0;
200
+ }
201
+ };
202
+ var transforms = {
203
+ /** Convert to number */
204
+ number: (value) => {
205
+ if (value === void 0 || value === null || value === "") return void 0;
206
+ const num = Number(value);
207
+ return isNaN(num) ? void 0 : num;
208
+ },
209
+ /** Convert to boolean */
210
+ boolean: (value) => {
211
+ if (value === void 0 || value === null) return void 0;
212
+ if (typeof value === "boolean") return value;
213
+ if (typeof value === "string") {
214
+ const lower = value.toLowerCase();
215
+ if (lower === "true" || lower === "1" || lower === "yes") return true;
216
+ if (lower === "false" || lower === "0" || lower === "no") return false;
217
+ }
218
+ return void 0;
219
+ },
220
+ /** Split comma-separated string into array */
221
+ commaSeparated: (value) => {
222
+ if (value === void 0 || value === null || value === "") return void 0;
223
+ if (typeof value !== "string") return void 0;
224
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
225
+ },
226
+ /** Parse JSON string */
227
+ json: (value) => {
228
+ if (value === void 0 || value === null || value === "") return void 0;
229
+ if (typeof value !== "string") return void 0;
230
+ try {
231
+ return JSON.parse(value);
232
+ } catch {
233
+ return void 0;
234
+ }
235
+ },
236
+ /** Parse date string */
237
+ date: (value) => {
238
+ if (value === void 0 || value === null || value === "") return void 0;
239
+ const date = new Date(String(value));
240
+ return isNaN(date.getTime()) ? void 0 : date;
241
+ },
242
+ /** Trim whitespace from string */
243
+ trim: (value) => {
244
+ if (value === void 0 || value === null) return void 0;
245
+ return String(value).trim() || void 0;
246
+ }
247
+ };
248
+
249
+ // src/client.ts
250
+ function createTriliumClient(config) {
251
+ const baseUrl = config.baseUrl.endsWith("/") ? config.baseUrl.slice(0, -1) : config.baseUrl;
252
+ return createClient({
253
+ baseUrl: `${baseUrl}/etapi`,
254
+ headers: {
255
+ Authorization: config.apiKey
256
+ }
257
+ });
258
+ }
259
+ var client_default = createTriliumClient;
260
+ export {
261
+ TriliumMapper,
262
+ buildSearchQuery,
263
+ client_default as createClient,
264
+ createTriliumClient,
265
+ transforms
266
+ };
package/package.json CHANGED
@@ -1,12 +1,42 @@
1
1
  {
2
2
  "name": "trilium-api",
3
- "version": "1.0.0",
4
- "description": "",
3
+ "version": "1.0.2",
4
+ "description": "A type-safe TypeScript client for the Trilium Notes ETAPI",
5
5
  "type": "module",
6
- "main": "index.js",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup src/index.ts --format cjs,esm --dts",
26
+ "demo": "tsx src/demo.ts",
27
+ "demo:search": "tsx src/demo-search.ts",
28
+ "demo:mapper": "tsx src/demo-mapper.ts",
29
+ "test": "vitest",
30
+ "test:run": "vitest run",
31
+ "test:coverage": "vitest run --coverage",
32
+ "generate-api": "openapi-typescript https://raw.githubusercontent.com/TriliumNext/Trilium/4bd8eeb52ad5b9fe70bbe98765f035e60a6a3bc0/apps/server/etapi.openapi.yaml -o ./src/generated/trilium.d.ts",
33
+ "test:ts": "tsc --noEmit",
34
+ "prepublishOnly": "pnpm build"
35
+ },
7
36
  "keywords": [],
8
37
  "author": "",
9
38
  "license": "AGPL-3.0",
39
+ "packageManager": "pnpm@10.25.0",
10
40
  "devDependencies": {
11
41
  "@types/node": "^25.0.3",
12
42
  "openapi-typescript": "^7.10.1",
@@ -17,16 +47,5 @@
17
47
  },
18
48
  "dependencies": {
19
49
  "openapi-fetch": "^0.15.0"
20
- },
21
- "scripts": {
22
- "build": "tsup src/index.ts --format cjs,esm --dts",
23
- "demo": "tsx src/demo.ts",
24
- "demo:search": "tsx src/demo-search.ts",
25
- "demo:mapper": "tsx src/demo-mapper.ts",
26
- "test": "vitest",
27
- "test:run": "vitest run",
28
- "test:coverage": "vitest run --coverage",
29
- "generate-api": "openapi-typescript https://raw.githubusercontent.com/TriliumNext/Trilium/4bd8eeb52ad5b9fe70bbe98765f035e60a6a3bc0/apps/server/etapi.openapi.yaml -o ./src/generated/trilium.d.ts",
30
- "test:ts": "tsc --noEmit"
31
50
  }
32
- }
51
+ }
@@ -1,37 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main, master]
6
- pull_request:
7
- branches: [main, master]
8
-
9
- jobs:
10
- test:
11
- runs-on: ubuntu-latest
12
-
13
- strategy:
14
- matrix:
15
- node-version: [18, 20, 22]
16
-
17
- steps:
18
- - name: Checkout repository
19
- uses: actions/checkout@v4
20
-
21
- - name: Install pnpm
22
- uses: pnpm/action-setup@v4
23
-
24
- - name: Setup Node.js ${{ matrix.node-version }}
25
- uses: actions/setup-node@v4
26
- with:
27
- node-version: ${{ matrix.node-version }}
28
- cache: 'pnpm'
29
-
30
- - name: Install dependencies
31
- run: pnpm install
32
-
33
- - name: Type check
34
- run: pnpm test:ts
35
-
36
- - name: Run tests
37
- run: pnpm test:run
@@ -1,86 +0,0 @@
1
- name: Publish to npm
2
-
3
- on:
4
- workflow_dispatch:
5
- inputs:
6
- version:
7
- description: 'Version bump type'
8
- required: true
9
- default: 'patch'
10
- type: choice
11
- options:
12
- - patch
13
- - minor
14
- - major
15
- force:
16
- description: 'Force publish (overwrite existing version)'
17
- required: false
18
- default: false
19
- type: boolean
20
-
21
- jobs:
22
- publish:
23
- runs-on: ubuntu-latest
24
-
25
- permissions:
26
- contents: write
27
- id-token: write # Required for npm trusted publishing (OIDC)
28
-
29
- steps:
30
- - name: Checkout repository
31
- uses: actions/checkout@v4
32
- with:
33
- fetch-depth: 0
34
- token: ${{ secrets.GITHUB_TOKEN }}
35
-
36
- - name: Configure Git
37
- run: |
38
- git config user.name "github-actions[bot]"
39
- git config user.email "github-actions[bot]@users.noreply.github.com"
40
-
41
- - name: Install pnpm
42
- uses: pnpm/action-setup@v4
43
-
44
- - name: Setup Node.js
45
- uses: actions/setup-node@v4
46
- with:
47
- node-version: 20
48
- cache: 'pnpm'
49
- registry-url: 'https://registry.npmjs.org'
50
-
51
- - name: Install dependencies
52
- run: pnpm install
53
-
54
- - name: Type check
55
- run: pnpm test:ts
56
-
57
- - name: Run tests
58
- run: pnpm test:run
59
-
60
- - name: Bump version
61
- id: version
62
- run: |
63
- pnpm version ${{ inputs.version }} --no-git-tag-version
64
- VERSION=$(node -p "require('./package.json').version")
65
- echo "version=$VERSION" >> $GITHUB_OUTPUT
66
-
67
- - name: Commit and tag
68
- run: |
69
- git add package.json
70
- git commit -m "chore: release v${{ steps.version.outputs.version }}"
71
- git tag "v${{ steps.version.outputs.version }}"
72
- git push && git push --tags
73
-
74
- - name: Build
75
- run: pnpm build
76
-
77
- - name: Publish to npm
78
- run: pnpm publish --access public --no-git-checks ${{ inputs.force && '--force' || '' }}
79
- # Uses OIDC trusted publishing - no NPM_TOKEN needed
80
-
81
- - name: Create GitHub Release
82
- uses: softprops/action-gh-release@v2
83
- with:
84
- tag_name: v${{ steps.version.outputs.version }}
85
- name: v${{ steps.version.outputs.version }}
86
- generate_release_notes: true