@infograb/notion-cli 5.9.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/LICENSE +21 -0
- package/README.md +1386 -0
- package/bin/dev +17 -0
- package/bin/dev.cmd +3 -0
- package/bin/run +14 -0
- package/bin/run.cmd +3 -0
- package/dist/base-command.d.ts +73 -0
- package/dist/base-command.js +179 -0
- package/dist/base-flags.d.ts +14 -0
- package/dist/base-flags.js +59 -0
- package/dist/cache.d.ts +84 -0
- package/dist/cache.js +351 -0
- package/dist/commands/batch/retrieve.d.ts +43 -0
- package/dist/commands/batch/retrieve.js +265 -0
- package/dist/commands/block/append.d.ts +42 -0
- package/dist/commands/block/append.js +219 -0
- package/dist/commands/block/delete.d.ts +30 -0
- package/dist/commands/block/delete.js +94 -0
- package/dist/commands/block/retrieve/children.d.ts +31 -0
- package/dist/commands/block/retrieve/children.js +174 -0
- package/dist/commands/block/retrieve.d.ts +30 -0
- package/dist/commands/block/retrieve.js +98 -0
- package/dist/commands/block/update.d.ts +45 -0
- package/dist/commands/block/update.js +241 -0
- package/dist/commands/cache/info.d.ts +19 -0
- package/dist/commands/cache/info.js +145 -0
- package/dist/commands/config/set-token.d.ts +30 -0
- package/dist/commands/config/set-token.js +201 -0
- package/dist/commands/db/create.d.ts +31 -0
- package/dist/commands/db/create.js +124 -0
- package/dist/commands/db/query.d.ts +41 -0
- package/dist/commands/db/query.js +355 -0
- package/dist/commands/db/retrieve.d.ts +33 -0
- package/dist/commands/db/retrieve.js +134 -0
- package/dist/commands/db/schema.d.ts +32 -0
- package/dist/commands/db/schema.js +308 -0
- package/dist/commands/db/update.d.ts +31 -0
- package/dist/commands/db/update.js +117 -0
- package/dist/commands/doctor.d.ts +50 -0
- package/dist/commands/doctor.js +420 -0
- package/dist/commands/init.d.ts +57 -0
- package/dist/commands/init.js +471 -0
- package/dist/commands/list.d.ts +29 -0
- package/dist/commands/list.js +184 -0
- package/dist/commands/page/create.d.ts +33 -0
- package/dist/commands/page/create.js +240 -0
- package/dist/commands/page/retrieve/property_item.d.ts +24 -0
- package/dist/commands/page/retrieve/property_item.js +72 -0
- package/dist/commands/page/retrieve.d.ts +36 -0
- package/dist/commands/page/retrieve.js +244 -0
- package/dist/commands/page/update.d.ts +34 -0
- package/dist/commands/page/update.js +184 -0
- package/dist/commands/search.d.ts +40 -0
- package/dist/commands/search.js +348 -0
- package/dist/commands/sync.d.ts +24 -0
- package/dist/commands/sync.js +183 -0
- package/dist/commands/user/list.d.ts +27 -0
- package/dist/commands/user/list.js +99 -0
- package/dist/commands/user/retrieve/bot.d.ts +28 -0
- package/dist/commands/user/retrieve/bot.js +96 -0
- package/dist/commands/user/retrieve.d.ts +30 -0
- package/dist/commands/user/retrieve.js +103 -0
- package/dist/commands/whoami.d.ts +19 -0
- package/dist/commands/whoami.js +175 -0
- package/dist/deduplication.d.ts +41 -0
- package/dist/deduplication.js +71 -0
- package/dist/envelope.d.ts +169 -0
- package/dist/envelope.js +257 -0
- package/dist/errors/enhanced-errors.d.ts +168 -0
- package/dist/errors/enhanced-errors.js +570 -0
- package/dist/errors/index.d.ts +18 -0
- package/dist/errors/index.js +33 -0
- package/dist/examples/cache-retry-examples.d.ts +64 -0
- package/dist/examples/cache-retry-examples.js +375 -0
- package/dist/helper.d.ts +102 -0
- package/dist/helper.js +885 -0
- package/dist/http-agent.d.ts +38 -0
- package/dist/http-agent.js +60 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/interface.d.ts +4 -0
- package/dist/interface.js +2 -0
- package/dist/notion.d.ts +144 -0
- package/dist/notion.js +547 -0
- package/dist/retry.d.ts +72 -0
- package/dist/retry.js +381 -0
- package/dist/utils/disk-cache.d.ts +80 -0
- package/dist/utils/disk-cache.js +291 -0
- package/dist/utils/markdown-to-blocks.d.ts +19 -0
- package/dist/utils/markdown-to-blocks.js +259 -0
- package/dist/utils/notion-resolver.d.ts +48 -0
- package/dist/utils/notion-resolver.js +262 -0
- package/dist/utils/notion-url-parser.d.ts +46 -0
- package/dist/utils/notion-url-parser.js +111 -0
- package/dist/utils/property-expander.d.ts +45 -0
- package/dist/utils/property-expander.js +323 -0
- package/dist/utils/schema-examples.d.ts +40 -0
- package/dist/utils/schema-examples.js +359 -0
- package/dist/utils/schema-extractor.d.ts +65 -0
- package/dist/utils/schema-extractor.js +235 -0
- package/dist/utils/table-formatter.d.ts +36 -0
- package/dist/utils/table-formatter.js +122 -0
- package/dist/utils/terminal-banner.d.ts +24 -0
- package/dist/utils/terminal-banner.js +34 -0
- package/dist/utils/token-validator.d.ts +55 -0
- package/dist/utils/token-validator.js +85 -0
- package/dist/utils/update-notifier.d.ts +26 -0
- package/dist/utils/update-notifier.js +54 -0
- package/dist/utils/workspace-cache.d.ts +58 -0
- package/dist/utils/workspace-cache.js +185 -0
- package/oclif.manifest.json +4497 -0
- package/package.json +115 -0
- package/scripts/banner.js +38 -0
- package/scripts/postinstall.js +56 -0
package/dist/cache.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Simple in-memory caching layer for Notion API responses
|
|
4
|
+
* Supports TTL (time-to-live) and cache invalidation
|
|
5
|
+
* Integrated with disk cache for persistence across CLI invocations
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.cacheManager = exports.CacheManager = void 0;
|
|
9
|
+
const disk_cache_1 = require("./utils/disk-cache");
|
|
10
|
+
/**
|
|
11
|
+
* Check if verbose logging is enabled
|
|
12
|
+
*/
|
|
13
|
+
function isVerboseEnabled() {
|
|
14
|
+
return process.env.DEBUG === 'true' ||
|
|
15
|
+
process.env.NOTION_CLI_DEBUG === 'true' ||
|
|
16
|
+
process.env.NOTION_CLI_VERBOSE === 'true';
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Log structured cache event to stderr
|
|
20
|
+
* Never pollutes stdout - safe for JSON output
|
|
21
|
+
*/
|
|
22
|
+
function logCacheEvent(event) {
|
|
23
|
+
// Only log if verbose mode is enabled
|
|
24
|
+
if (!isVerboseEnabled()) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Always write to stderr, never stdout
|
|
28
|
+
console.error(JSON.stringify(event));
|
|
29
|
+
}
|
|
30
|
+
class CacheManager {
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.cache = new Map();
|
|
33
|
+
this.stats = {
|
|
34
|
+
hits: 0,
|
|
35
|
+
misses: 0,
|
|
36
|
+
sets: 0,
|
|
37
|
+
evictions: 0,
|
|
38
|
+
size: 0,
|
|
39
|
+
};
|
|
40
|
+
// Default configuration
|
|
41
|
+
this.config = {
|
|
42
|
+
enabled: process.env.NOTION_CLI_CACHE_ENABLED !== 'false',
|
|
43
|
+
defaultTtl: parseInt(process.env.NOTION_CLI_CACHE_TTL || '300000', 10), // 5 minutes default
|
|
44
|
+
maxSize: parseInt(process.env.NOTION_CLI_CACHE_MAX_SIZE || '1000', 10),
|
|
45
|
+
ttlByType: {
|
|
46
|
+
dataSource: parseInt(process.env.NOTION_CLI_CACHE_DS_TTL || '600000', 10), // 10 min
|
|
47
|
+
database: parseInt(process.env.NOTION_CLI_CACHE_DB_TTL || '600000', 10), // 10 min
|
|
48
|
+
user: parseInt(process.env.NOTION_CLI_CACHE_USER_TTL || '3600000', 10), // 1 hour
|
|
49
|
+
page: parseInt(process.env.NOTION_CLI_CACHE_PAGE_TTL || '60000', 10), // 1 min
|
|
50
|
+
block: parseInt(process.env.NOTION_CLI_CACHE_BLOCK_TTL || '30000', 10), // 30 sec
|
|
51
|
+
},
|
|
52
|
+
...config,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Generate a cache key from resource type and identifiers
|
|
57
|
+
*/
|
|
58
|
+
generateKey(type, ...identifiers) {
|
|
59
|
+
return `${type}:${identifiers.map(id => typeof id === 'object' ? JSON.stringify(id) : String(id)).join(':')}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if a cache entry is still valid
|
|
63
|
+
*/
|
|
64
|
+
isValid(entry) {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
return now - entry.timestamp < entry.ttl;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Evict expired entries
|
|
70
|
+
*/
|
|
71
|
+
evictExpired() {
|
|
72
|
+
let evictedCount = 0;
|
|
73
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
74
|
+
if (!this.isValid(entry)) {
|
|
75
|
+
this.cache.delete(key);
|
|
76
|
+
this.stats.evictions++;
|
|
77
|
+
evictedCount++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this.stats.size = this.cache.size;
|
|
81
|
+
// Log eviction event if any entries were evicted
|
|
82
|
+
if (evictedCount > 0 && isVerboseEnabled()) {
|
|
83
|
+
logCacheEvent({
|
|
84
|
+
level: 'debug',
|
|
85
|
+
event: 'cache_evict',
|
|
86
|
+
namespace: 'expired',
|
|
87
|
+
cache_size: this.cache.size,
|
|
88
|
+
timestamp: new Date().toISOString(),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Evict oldest entries if cache is full
|
|
94
|
+
*/
|
|
95
|
+
evictOldest() {
|
|
96
|
+
if (this.cache.size >= this.config.maxSize) {
|
|
97
|
+
// Find and remove oldest entry
|
|
98
|
+
let oldestKey = null;
|
|
99
|
+
let oldestTime = Infinity;
|
|
100
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
101
|
+
if (entry.timestamp < oldestTime) {
|
|
102
|
+
oldestTime = entry.timestamp;
|
|
103
|
+
oldestKey = key;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (oldestKey) {
|
|
107
|
+
this.cache.delete(oldestKey);
|
|
108
|
+
this.stats.evictions++;
|
|
109
|
+
// Log LRU eviction
|
|
110
|
+
logCacheEvent({
|
|
111
|
+
level: 'debug',
|
|
112
|
+
event: 'cache_evict',
|
|
113
|
+
namespace: 'lru',
|
|
114
|
+
key: oldestKey,
|
|
115
|
+
cache_size: this.cache.size,
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get a value from cache (checks memory, then disk)
|
|
123
|
+
*/
|
|
124
|
+
async get(type, ...identifiers) {
|
|
125
|
+
if (!this.config.enabled) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const key = this.generateKey(type, ...identifiers);
|
|
129
|
+
const entry = this.cache.get(key);
|
|
130
|
+
// Check memory cache first
|
|
131
|
+
if (entry && this.isValid(entry)) {
|
|
132
|
+
this.stats.hits++;
|
|
133
|
+
// Log cache hit
|
|
134
|
+
logCacheEvent({
|
|
135
|
+
level: 'debug',
|
|
136
|
+
event: 'cache_hit',
|
|
137
|
+
namespace: type,
|
|
138
|
+
key: identifiers.join(':'),
|
|
139
|
+
age_ms: Date.now() - entry.timestamp,
|
|
140
|
+
ttl_ms: entry.ttl,
|
|
141
|
+
timestamp: new Date().toISOString(),
|
|
142
|
+
});
|
|
143
|
+
return entry.data;
|
|
144
|
+
}
|
|
145
|
+
// Remove invalid memory entry
|
|
146
|
+
if (entry) {
|
|
147
|
+
this.cache.delete(key);
|
|
148
|
+
this.stats.evictions++;
|
|
149
|
+
// Log eviction event
|
|
150
|
+
logCacheEvent({
|
|
151
|
+
level: 'debug',
|
|
152
|
+
event: 'cache_evict',
|
|
153
|
+
namespace: type,
|
|
154
|
+
key: identifiers.join(':'),
|
|
155
|
+
cache_size: this.cache.size,
|
|
156
|
+
timestamp: new Date().toISOString(),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Check disk cache (only if enabled)
|
|
160
|
+
const diskEnabled = process.env.NOTION_CLI_DISK_CACHE_ENABLED !== 'false';
|
|
161
|
+
if (diskEnabled) {
|
|
162
|
+
try {
|
|
163
|
+
const diskEntry = await disk_cache_1.diskCacheManager.get(key);
|
|
164
|
+
if (diskEntry && diskEntry.data) {
|
|
165
|
+
const entry = diskEntry.data;
|
|
166
|
+
// Validate disk entry
|
|
167
|
+
if (this.isValid(entry)) {
|
|
168
|
+
// Promote to memory cache
|
|
169
|
+
this.cache.set(key, entry);
|
|
170
|
+
this.stats.hits++;
|
|
171
|
+
// Log cache hit (from disk)
|
|
172
|
+
logCacheEvent({
|
|
173
|
+
level: 'debug',
|
|
174
|
+
event: 'cache_hit',
|
|
175
|
+
namespace: type,
|
|
176
|
+
key: identifiers.join(':'),
|
|
177
|
+
age_ms: Date.now() - entry.timestamp,
|
|
178
|
+
ttl_ms: entry.ttl,
|
|
179
|
+
timestamp: new Date().toISOString(),
|
|
180
|
+
});
|
|
181
|
+
return entry.data;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Remove expired disk entry
|
|
185
|
+
disk_cache_1.diskCacheManager.invalidate(key).catch(() => { });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
// Silently ignore disk cache errors
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Cache miss
|
|
194
|
+
this.stats.misses++;
|
|
195
|
+
// Log cache miss
|
|
196
|
+
logCacheEvent({
|
|
197
|
+
level: 'debug',
|
|
198
|
+
event: 'cache_miss',
|
|
199
|
+
namespace: type,
|
|
200
|
+
key: identifiers.join(':'),
|
|
201
|
+
timestamp: new Date().toISOString(),
|
|
202
|
+
});
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Set a value in cache with optional custom TTL (writes to memory and disk)
|
|
207
|
+
*/
|
|
208
|
+
set(type, data, customTtl, ...identifiers) {
|
|
209
|
+
if (!this.config.enabled) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// Evict expired entries periodically
|
|
213
|
+
if (this.cache.size > 0 && Math.random() < 0.1) {
|
|
214
|
+
this.evictExpired();
|
|
215
|
+
}
|
|
216
|
+
// Evict oldest if at capacity
|
|
217
|
+
this.evictOldest();
|
|
218
|
+
const key = this.generateKey(type, ...identifiers);
|
|
219
|
+
const ttl = customTtl || this.config.ttlByType[type] || this.config.defaultTtl;
|
|
220
|
+
const entry = {
|
|
221
|
+
data,
|
|
222
|
+
timestamp: Date.now(),
|
|
223
|
+
ttl,
|
|
224
|
+
};
|
|
225
|
+
this.cache.set(key, entry);
|
|
226
|
+
this.stats.sets++;
|
|
227
|
+
this.stats.size = this.cache.size;
|
|
228
|
+
// Log cache set
|
|
229
|
+
logCacheEvent({
|
|
230
|
+
level: 'debug',
|
|
231
|
+
event: 'cache_set',
|
|
232
|
+
namespace: type,
|
|
233
|
+
key: identifiers.join(':'),
|
|
234
|
+
ttl_ms: ttl,
|
|
235
|
+
cache_size: this.cache.size,
|
|
236
|
+
timestamp: new Date().toISOString(),
|
|
237
|
+
});
|
|
238
|
+
// Async write to disk cache (fire-and-forget)
|
|
239
|
+
const diskEnabled = process.env.NOTION_CLI_DISK_CACHE_ENABLED !== 'false';
|
|
240
|
+
if (diskEnabled) {
|
|
241
|
+
disk_cache_1.diskCacheManager.set(key, entry, ttl).catch(() => {
|
|
242
|
+
// Silently ignore disk cache errors
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Invalidate specific cache entries by type and optional identifiers
|
|
248
|
+
*/
|
|
249
|
+
invalidate(type, ...identifiers) {
|
|
250
|
+
const diskEnabled = process.env.NOTION_CLI_DISK_CACHE_ENABLED !== 'false';
|
|
251
|
+
if (identifiers.length === 0) {
|
|
252
|
+
// Invalidate all entries of this type
|
|
253
|
+
const pattern = `${type}:`;
|
|
254
|
+
let invalidatedCount = 0;
|
|
255
|
+
for (const key of this.cache.keys()) {
|
|
256
|
+
if (key.startsWith(pattern)) {
|
|
257
|
+
this.cache.delete(key);
|
|
258
|
+
this.stats.evictions++;
|
|
259
|
+
invalidatedCount++;
|
|
260
|
+
// Also invalidate from disk (fire-and-forget)
|
|
261
|
+
if (diskEnabled) {
|
|
262
|
+
disk_cache_1.diskCacheManager.invalidate(key).catch(() => { });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Log bulk invalidation
|
|
267
|
+
if (invalidatedCount > 0) {
|
|
268
|
+
logCacheEvent({
|
|
269
|
+
level: 'debug',
|
|
270
|
+
event: 'cache_invalidate',
|
|
271
|
+
namespace: type,
|
|
272
|
+
cache_size: this.cache.size,
|
|
273
|
+
timestamp: new Date().toISOString(),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
// Invalidate specific entry
|
|
279
|
+
const key = this.generateKey(type, ...identifiers);
|
|
280
|
+
if (this.cache.delete(key)) {
|
|
281
|
+
this.stats.evictions++;
|
|
282
|
+
// Also invalidate from disk (fire-and-forget)
|
|
283
|
+
if (diskEnabled) {
|
|
284
|
+
disk_cache_1.diskCacheManager.invalidate(key).catch(() => { });
|
|
285
|
+
}
|
|
286
|
+
// Log specific invalidation
|
|
287
|
+
logCacheEvent({
|
|
288
|
+
level: 'debug',
|
|
289
|
+
event: 'cache_invalidate',
|
|
290
|
+
namespace: type,
|
|
291
|
+
key: identifiers.join(':'),
|
|
292
|
+
cache_size: this.cache.size,
|
|
293
|
+
timestamp: new Date().toISOString(),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
this.stats.size = this.cache.size;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Clear all cache entries (memory and disk)
|
|
301
|
+
*/
|
|
302
|
+
clear() {
|
|
303
|
+
const previousSize = this.cache.size;
|
|
304
|
+
this.cache.clear();
|
|
305
|
+
this.stats.evictions += this.stats.size;
|
|
306
|
+
this.stats.size = 0;
|
|
307
|
+
// Also clear disk cache (fire-and-forget)
|
|
308
|
+
const diskEnabled = process.env.NOTION_CLI_DISK_CACHE_ENABLED !== 'false';
|
|
309
|
+
if (diskEnabled) {
|
|
310
|
+
disk_cache_1.diskCacheManager.clear().catch(() => { });
|
|
311
|
+
}
|
|
312
|
+
// Log cache clear
|
|
313
|
+
if (previousSize > 0) {
|
|
314
|
+
logCacheEvent({
|
|
315
|
+
level: 'info',
|
|
316
|
+
event: 'cache_invalidate',
|
|
317
|
+
namespace: 'all',
|
|
318
|
+
cache_size: 0,
|
|
319
|
+
timestamp: new Date().toISOString(),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get cache statistics
|
|
325
|
+
*/
|
|
326
|
+
getStats() {
|
|
327
|
+
return { ...this.stats };
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get cache hit rate
|
|
331
|
+
*/
|
|
332
|
+
getHitRate() {
|
|
333
|
+
const total = this.stats.hits + this.stats.misses;
|
|
334
|
+
return total > 0 ? this.stats.hits / total : 0;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Check if cache is enabled
|
|
338
|
+
*/
|
|
339
|
+
isEnabled() {
|
|
340
|
+
return this.config.enabled;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Get current configuration
|
|
344
|
+
*/
|
|
345
|
+
getConfig() {
|
|
346
|
+
return { ...this.config };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
exports.CacheManager = CacheManager;
|
|
350
|
+
// Singleton instance
|
|
351
|
+
exports.cacheManager = new CacheManager();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class BatchRetrieve extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static aliases: string[];
|
|
5
|
+
static examples: {
|
|
6
|
+
description: string;
|
|
7
|
+
command: string;
|
|
8
|
+
}[];
|
|
9
|
+
static args: {
|
|
10
|
+
ids: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
11
|
+
};
|
|
12
|
+
static flags: {
|
|
13
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
'page-size': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
|
+
retry: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
timeout: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
|
+
'no-cache': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
minimal: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
20
|
+
markdown: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
21
|
+
'compact-json': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
pretty: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
23
|
+
columns: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
24
|
+
sort: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
|
+
filter: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
26
|
+
csv: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
27
|
+
extended: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
28
|
+
'no-truncate': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
29
|
+
'no-header': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
30
|
+
ids: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
31
|
+
type: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
32
|
+
raw: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Read IDs from stdin
|
|
36
|
+
*/
|
|
37
|
+
private readStdin;
|
|
38
|
+
/**
|
|
39
|
+
* Retrieve a single resource and handle errors
|
|
40
|
+
*/
|
|
41
|
+
private retrieveResource;
|
|
42
|
+
run(): Promise<void>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const table_formatter_1 = require("../../utils/table-formatter");
|
|
5
|
+
const notion = require("../../notion");
|
|
6
|
+
const helper_1 = require("../../helper");
|
|
7
|
+
const base_flags_1 = require("../../base-flags");
|
|
8
|
+
const errors_1 = require("../../errors");
|
|
9
|
+
const client_1 = require("@notionhq/client");
|
|
10
|
+
const readline = require("readline");
|
|
11
|
+
class BatchRetrieve extends core_1.Command {
|
|
12
|
+
/**
|
|
13
|
+
* Read IDs from stdin
|
|
14
|
+
*/
|
|
15
|
+
async readStdin() {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const ids = [];
|
|
18
|
+
const rl = readline.createInterface({
|
|
19
|
+
input: process.stdin,
|
|
20
|
+
output: process.stdout,
|
|
21
|
+
terminal: false,
|
|
22
|
+
});
|
|
23
|
+
rl.on('line', (line) => {
|
|
24
|
+
const trimmed = line.trim();
|
|
25
|
+
if (trimmed) {
|
|
26
|
+
ids.push(trimmed);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
rl.on('close', () => {
|
|
30
|
+
resolve(ids);
|
|
31
|
+
});
|
|
32
|
+
rl.on('error', (err) => {
|
|
33
|
+
reject(err);
|
|
34
|
+
});
|
|
35
|
+
// Timeout after 5 seconds if no input
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
rl.close();
|
|
38
|
+
resolve(ids);
|
|
39
|
+
}, 5000);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Retrieve a single resource and handle errors
|
|
44
|
+
*/
|
|
45
|
+
async retrieveResource(id, type) {
|
|
46
|
+
try {
|
|
47
|
+
let data;
|
|
48
|
+
switch (type) {
|
|
49
|
+
case 'page': {
|
|
50
|
+
const pageResponse = await notion.retrievePage({ page_id: id });
|
|
51
|
+
if (!(0, client_1.isFullPage)(pageResponse)) {
|
|
52
|
+
throw new errors_1.NotionCLIError(errors_1.NotionCLIErrorCode.API_ERROR, 'Received partial page response instead of full page', [], { attemptedId: id });
|
|
53
|
+
}
|
|
54
|
+
data = pageResponse;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case 'block': {
|
|
58
|
+
const blockResponse = await notion.retrieveBlock(id);
|
|
59
|
+
if (!(0, client_1.isFullBlock)(blockResponse)) {
|
|
60
|
+
throw new errors_1.NotionCLIError(errors_1.NotionCLIErrorCode.API_ERROR, 'Received partial block response instead of full block', [], { attemptedId: id });
|
|
61
|
+
}
|
|
62
|
+
data = blockResponse;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'database':
|
|
66
|
+
data = await notion.retrieveDataSource(id);
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
throw new errors_1.NotionCLIError(errors_1.NotionCLIErrorCode.VALIDATION_ERROR, `Invalid resource type: ${type}`, [], { userInput: type, resourceType: type });
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
id,
|
|
73
|
+
success: true,
|
|
74
|
+
data,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const cliError = error instanceof errors_1.NotionCLIError
|
|
79
|
+
? error
|
|
80
|
+
: (0, errors_1.wrapNotionError)(error, {
|
|
81
|
+
attemptedId: id,
|
|
82
|
+
userInput: id
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
id,
|
|
86
|
+
success: false,
|
|
87
|
+
error: cliError.code,
|
|
88
|
+
message: cliError.userMessage,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async run() {
|
|
93
|
+
const { args, flags } = await this.parse(BatchRetrieve);
|
|
94
|
+
try {
|
|
95
|
+
// Get IDs from args, flags, or stdin
|
|
96
|
+
let ids = [];
|
|
97
|
+
if (args.ids) {
|
|
98
|
+
// From positional argument
|
|
99
|
+
ids = args.ids.split(',').map(id => id.trim()).filter(id => id);
|
|
100
|
+
}
|
|
101
|
+
else if (flags.ids) {
|
|
102
|
+
// From --ids flag
|
|
103
|
+
ids = flags.ids.split(',').map(id => id.trim()).filter(id => id);
|
|
104
|
+
}
|
|
105
|
+
else if (!process.stdin.isTTY) {
|
|
106
|
+
// From stdin
|
|
107
|
+
ids = await this.readStdin();
|
|
108
|
+
}
|
|
109
|
+
if (ids.length === 0) {
|
|
110
|
+
throw new errors_1.NotionCLIError(errors_1.NotionCLIErrorCode.VALIDATION_ERROR, 'No IDs provided. Use --ids flag, positional argument, or pipe IDs via stdin', [
|
|
111
|
+
{
|
|
112
|
+
description: 'Provide IDs via --ids flag',
|
|
113
|
+
command: 'notion-cli batch retrieve --ids ID1,ID2,ID3'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
description: 'Or pipe IDs from a file',
|
|
117
|
+
command: 'cat ids.txt | notion-cli batch retrieve'
|
|
118
|
+
}
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
// Fetch all resources in parallel
|
|
122
|
+
const results = await Promise.all(ids.map(id => this.retrieveResource(id, flags.type)));
|
|
123
|
+
// Count successes and failures
|
|
124
|
+
const successCount = results.filter(r => r.success).length;
|
|
125
|
+
const failureCount = results.filter(r => !r.success).length;
|
|
126
|
+
// Handle JSON output for automation (takes precedence)
|
|
127
|
+
if (flags.json) {
|
|
128
|
+
this.log(JSON.stringify({
|
|
129
|
+
success: successCount > 0,
|
|
130
|
+
total: results.length,
|
|
131
|
+
succeeded: successCount,
|
|
132
|
+
failed: failureCount,
|
|
133
|
+
results: results,
|
|
134
|
+
timestamp: new Date().toISOString(),
|
|
135
|
+
}, null, 2));
|
|
136
|
+
process.exit(failureCount === 0 ? 0 : 1);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Handle compact JSON output
|
|
140
|
+
if (flags['compact-json']) {
|
|
141
|
+
(0, helper_1.outputCompactJson)({
|
|
142
|
+
total: results.length,
|
|
143
|
+
succeeded: successCount,
|
|
144
|
+
failed: failureCount,
|
|
145
|
+
results: results,
|
|
146
|
+
});
|
|
147
|
+
process.exit(failureCount === 0 ? 0 : 1);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Handle raw JSON output
|
|
151
|
+
if (flags.raw) {
|
|
152
|
+
(0, helper_1.outputRawJson)(results);
|
|
153
|
+
process.exit(failureCount === 0 ? 0 : 1);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Handle table output (default)
|
|
157
|
+
const tableData = results.map(result => {
|
|
158
|
+
if (result.success && result.data) {
|
|
159
|
+
let title = '';
|
|
160
|
+
if ('object' in result.data) {
|
|
161
|
+
if (result.data.object === 'page') {
|
|
162
|
+
title = (0, helper_1.getPageTitle)(result.data);
|
|
163
|
+
}
|
|
164
|
+
else if (result.data.object === 'data_source') {
|
|
165
|
+
title = (0, helper_1.getDataSourceTitle)(result.data);
|
|
166
|
+
}
|
|
167
|
+
else if (result.data.object === 'block') {
|
|
168
|
+
title = (0, helper_1.getBlockPlainText)(result.data);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
id: result.id,
|
|
173
|
+
status: 'success',
|
|
174
|
+
type: result.data.object || flags.type,
|
|
175
|
+
title: title || '-',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
return {
|
|
180
|
+
id: result.id,
|
|
181
|
+
status: 'failed',
|
|
182
|
+
type: flags.type,
|
|
183
|
+
title: result.message || result.error || 'Unknown error',
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
const columns = {
|
|
188
|
+
id: {},
|
|
189
|
+
status: {},
|
|
190
|
+
type: {},
|
|
191
|
+
title: {},
|
|
192
|
+
};
|
|
193
|
+
const options = {
|
|
194
|
+
printLine: this.log.bind(this),
|
|
195
|
+
...flags,
|
|
196
|
+
};
|
|
197
|
+
(0, table_formatter_1.formatTable)(tableData, columns, options);
|
|
198
|
+
// Print summary
|
|
199
|
+
this.log(`\nTotal: ${results.length} | Succeeded: ${successCount} | Failed: ${failureCount}`);
|
|
200
|
+
process.exit(failureCount === 0 ? 0 : 1);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
const cliError = error instanceof errors_1.NotionCLIError
|
|
204
|
+
? error
|
|
205
|
+
: (0, errors_1.wrapNotionError)(error, {
|
|
206
|
+
endpoint: 'batch.retrieve'
|
|
207
|
+
});
|
|
208
|
+
if (flags.json) {
|
|
209
|
+
this.log(JSON.stringify(cliError.toJSON(), null, 2));
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
this.error(cliError.toHumanString());
|
|
213
|
+
}
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
BatchRetrieve.description = 'Batch retrieve multiple pages, blocks, or data sources';
|
|
219
|
+
BatchRetrieve.aliases = ['batch:r'];
|
|
220
|
+
BatchRetrieve.examples = [
|
|
221
|
+
{
|
|
222
|
+
description: 'Retrieve multiple pages via --ids flag',
|
|
223
|
+
command: '$ notion-cli batch retrieve --ids PAGE_ID_1,PAGE_ID_2,PAGE_ID_3 --compact-json',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
description: 'Retrieve multiple pages from stdin (one ID per line)',
|
|
227
|
+
command: '$ cat page_ids.txt | notion-cli batch retrieve --compact-json',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
description: 'Retrieve multiple blocks',
|
|
231
|
+
command: '$ notion-cli batch retrieve --ids BLOCK_ID_1,BLOCK_ID_2 --type block --json',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
description: 'Retrieve multiple data sources',
|
|
235
|
+
command: '$ notion-cli batch retrieve --ids DS_ID_1,DS_ID_2 --type database --json',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
description: 'Retrieve with raw output',
|
|
239
|
+
command: '$ notion-cli batch retrieve --ids ID1,ID2,ID3 -r',
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
BatchRetrieve.args = {
|
|
243
|
+
ids: core_1.Args.string({
|
|
244
|
+
required: false,
|
|
245
|
+
description: 'Comma-separated list of IDs to retrieve (or use --ids flag or stdin)',
|
|
246
|
+
}),
|
|
247
|
+
};
|
|
248
|
+
BatchRetrieve.flags = {
|
|
249
|
+
ids: core_1.Flags.string({
|
|
250
|
+
description: 'Comma-separated list of IDs to retrieve',
|
|
251
|
+
}),
|
|
252
|
+
type: core_1.Flags.string({
|
|
253
|
+
description: 'Resource type to retrieve (page, block, database)',
|
|
254
|
+
options: ['page', 'block', 'database'],
|
|
255
|
+
default: 'page',
|
|
256
|
+
}),
|
|
257
|
+
raw: core_1.Flags.boolean({
|
|
258
|
+
char: 'r',
|
|
259
|
+
description: 'output raw json (recommended for AI assistants - returns all fields)',
|
|
260
|
+
}),
|
|
261
|
+
...table_formatter_1.tableFlags,
|
|
262
|
+
...base_flags_1.OutputFormatFlags,
|
|
263
|
+
...base_flags_1.AutomationFlags,
|
|
264
|
+
};
|
|
265
|
+
exports.default = BatchRetrieve;
|