@catafal/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.
Files changed (162) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +552 -0
  3. package/bin/dev +17 -0
  4. package/bin/dev.cmd +3 -0
  5. package/bin/run +14 -0
  6. package/bin/run.cmd +3 -0
  7. package/dist/base-command.d.ts +73 -0
  8. package/dist/base-command.js +179 -0
  9. package/dist/base-flags.d.ts +14 -0
  10. package/dist/base-flags.js +59 -0
  11. package/dist/cache.d.ts +84 -0
  12. package/dist/cache.js +351 -0
  13. package/dist/commands/append.d.ts +37 -0
  14. package/dist/commands/append.js +120 -0
  15. package/dist/commands/batch/delete.d.ts +42 -0
  16. package/dist/commands/batch/delete.js +199 -0
  17. package/dist/commands/batch/retrieve.d.ts +43 -0
  18. package/dist/commands/batch/retrieve.js +272 -0
  19. package/dist/commands/block/append.d.ts +42 -0
  20. package/dist/commands/block/append.js +219 -0
  21. package/dist/commands/block/delete.d.ts +30 -0
  22. package/dist/commands/block/delete.js +97 -0
  23. package/dist/commands/block/retrieve/children.d.ts +31 -0
  24. package/dist/commands/block/retrieve/children.js +177 -0
  25. package/dist/commands/block/retrieve.d.ts +30 -0
  26. package/dist/commands/block/retrieve.js +101 -0
  27. package/dist/commands/block/update.d.ts +45 -0
  28. package/dist/commands/block/update.js +242 -0
  29. package/dist/commands/bookmark/list.d.ts +30 -0
  30. package/dist/commands/bookmark/list.js +60 -0
  31. package/dist/commands/bookmark/remove.d.ts +26 -0
  32. package/dist/commands/bookmark/remove.js +47 -0
  33. package/dist/commands/bookmark/set.d.ts +29 -0
  34. package/dist/commands/bookmark/set.js +96 -0
  35. package/dist/commands/browse.d.ts +13 -0
  36. package/dist/commands/browse.js +44 -0
  37. package/dist/commands/cache/info.d.ts +19 -0
  38. package/dist/commands/cache/info.js +145 -0
  39. package/dist/commands/config/set-token.d.ts +22 -0
  40. package/dist/commands/config/set-token.js +137 -0
  41. package/dist/commands/daily/index.d.ts +32 -0
  42. package/dist/commands/daily/index.js +135 -0
  43. package/dist/commands/daily/setup.d.ts +42 -0
  44. package/dist/commands/daily/setup.js +149 -0
  45. package/dist/commands/db/create.d.ts +31 -0
  46. package/dist/commands/db/create.js +124 -0
  47. package/dist/commands/db/query.d.ts +41 -0
  48. package/dist/commands/db/query.js +360 -0
  49. package/dist/commands/db/retrieve.d.ts +33 -0
  50. package/dist/commands/db/retrieve.js +134 -0
  51. package/dist/commands/db/schema.d.ts +32 -0
  52. package/dist/commands/db/schema.js +308 -0
  53. package/dist/commands/db/update.d.ts +31 -0
  54. package/dist/commands/db/update.js +117 -0
  55. package/dist/commands/doctor.d.ts +50 -0
  56. package/dist/commands/doctor.js +420 -0
  57. package/dist/commands/init.d.ts +65 -0
  58. package/dist/commands/init.js +479 -0
  59. package/dist/commands/list.d.ts +29 -0
  60. package/dist/commands/list.js +219 -0
  61. package/dist/commands/open.d.ts +29 -0
  62. package/dist/commands/open.js +100 -0
  63. package/dist/commands/page/create.d.ts +33 -0
  64. package/dist/commands/page/create.js +261 -0
  65. package/dist/commands/page/delete.d.ts +36 -0
  66. package/dist/commands/page/delete.js +107 -0
  67. package/dist/commands/page/export.d.ts +38 -0
  68. package/dist/commands/page/export.js +120 -0
  69. package/dist/commands/page/retrieve/property_item.d.ts +24 -0
  70. package/dist/commands/page/retrieve/property_item.js +75 -0
  71. package/dist/commands/page/retrieve.d.ts +36 -0
  72. package/dist/commands/page/retrieve.js +244 -0
  73. package/dist/commands/page/update.d.ts +34 -0
  74. package/dist/commands/page/update.js +184 -0
  75. package/dist/commands/quick.d.ts +35 -0
  76. package/dist/commands/quick.js +168 -0
  77. package/dist/commands/search.d.ts +43 -0
  78. package/dist/commands/search.js +361 -0
  79. package/dist/commands/stats.d.ts +35 -0
  80. package/dist/commands/stats.js +274 -0
  81. package/dist/commands/sync.d.ts +24 -0
  82. package/dist/commands/sync.js +183 -0
  83. package/dist/commands/template/get.d.ts +28 -0
  84. package/dist/commands/template/get.js +59 -0
  85. package/dist/commands/template/list.d.ts +32 -0
  86. package/dist/commands/template/list.js +62 -0
  87. package/dist/commands/template/remove.d.ts +27 -0
  88. package/dist/commands/template/remove.js +48 -0
  89. package/dist/commands/template/save.d.ts +32 -0
  90. package/dist/commands/template/save.js +92 -0
  91. package/dist/commands/template/use.d.ts +34 -0
  92. package/dist/commands/template/use.js +142 -0
  93. package/dist/commands/user/list.d.ts +27 -0
  94. package/dist/commands/user/list.js +99 -0
  95. package/dist/commands/user/retrieve/bot.d.ts +28 -0
  96. package/dist/commands/user/retrieve/bot.js +96 -0
  97. package/dist/commands/user/retrieve.d.ts +30 -0
  98. package/dist/commands/user/retrieve.js +103 -0
  99. package/dist/commands/whoami.d.ts +19 -0
  100. package/dist/commands/whoami.js +175 -0
  101. package/dist/deduplication.d.ts +41 -0
  102. package/dist/deduplication.js +71 -0
  103. package/dist/envelope.d.ts +169 -0
  104. package/dist/envelope.js +257 -0
  105. package/dist/errors/enhanced-errors.d.ts +168 -0
  106. package/dist/errors/enhanced-errors.js +567 -0
  107. package/dist/errors/index.d.ts +18 -0
  108. package/dist/errors/index.js +33 -0
  109. package/dist/examples/cache-retry-examples.d.ts +64 -0
  110. package/dist/examples/cache-retry-examples.js +375 -0
  111. package/dist/helper.d.ts +102 -0
  112. package/dist/helper.js +885 -0
  113. package/dist/http-agent.d.ts +38 -0
  114. package/dist/http-agent.js +60 -0
  115. package/dist/index.d.ts +1 -0
  116. package/dist/index.js +4 -0
  117. package/dist/interface.d.ts +4 -0
  118. package/dist/interface.js +2 -0
  119. package/dist/notion.d.ts +144 -0
  120. package/dist/notion.js +547 -0
  121. package/dist/retry.d.ts +72 -0
  122. package/dist/retry.js +381 -0
  123. package/dist/utils/bookmarks.d.ts +32 -0
  124. package/dist/utils/bookmarks.js +98 -0
  125. package/dist/utils/daily-config.d.ts +22 -0
  126. package/dist/utils/daily-config.js +60 -0
  127. package/dist/utils/disk-cache.d.ts +80 -0
  128. package/dist/utils/disk-cache.js +291 -0
  129. package/dist/utils/fuzzy.d.ts +36 -0
  130. package/dist/utils/fuzzy.js +69 -0
  131. package/dist/utils/interactive-navigator.d.ts +63 -0
  132. package/dist/utils/interactive-navigator.js +123 -0
  133. package/dist/utils/markdown-to-blocks.d.ts +21 -0
  134. package/dist/utils/markdown-to-blocks.js +333 -0
  135. package/dist/utils/notion-resolver.d.ts +49 -0
  136. package/dist/utils/notion-resolver.js +278 -0
  137. package/dist/utils/notion-url-parser.d.ts +48 -0
  138. package/dist/utils/notion-url-parser.js +121 -0
  139. package/dist/utils/property-expander.d.ts +45 -0
  140. package/dist/utils/property-expander.js +323 -0
  141. package/dist/utils/schema-examples.d.ts +40 -0
  142. package/dist/utils/schema-examples.js +359 -0
  143. package/dist/utils/schema-extractor.d.ts +65 -0
  144. package/dist/utils/schema-extractor.js +235 -0
  145. package/dist/utils/shell-config.d.ts +30 -0
  146. package/dist/utils/shell-config.js +84 -0
  147. package/dist/utils/table-formatter.d.ts +36 -0
  148. package/dist/utils/table-formatter.js +125 -0
  149. package/dist/utils/templates.d.ts +30 -0
  150. package/dist/utils/templates.js +82 -0
  151. package/dist/utils/terminal-banner.d.ts +24 -0
  152. package/dist/utils/terminal-banner.js +34 -0
  153. package/dist/utils/token-validator.d.ts +42 -0
  154. package/dist/utils/token-validator.js +66 -0
  155. package/dist/utils/update-notifier.d.ts +26 -0
  156. package/dist/utils/update-notifier.js +54 -0
  157. package/dist/utils/workspace-cache.d.ts +58 -0
  158. package/dist/utils/workspace-cache.js +185 -0
  159. package/oclif.manifest.json +6471 -0
  160. package/package.json +118 -0
  161. package/scripts/banner.js +38 -0
  162. package/scripts/postinstall.js +44 -0
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ /**
3
+ * Base Command with Envelope Support
4
+ *
5
+ * Extends oclif Command with automatic envelope wrapping for consistent JSON output.
6
+ * All commands should extend this class to get automatic envelope support.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.EnvelopeFlags = exports.BaseCommand = void 0;
10
+ const core_1 = require("@oclif/core");
11
+ const envelope_1 = require("./envelope");
12
+ const index_1 = require("./errors/index");
13
+ const disk_cache_1 = require("./utils/disk-cache");
14
+ const http_agent_1 = require("./http-agent");
15
+ /**
16
+ * BaseCommand - Extends oclif Command with envelope support
17
+ *
18
+ * Features:
19
+ * - Automatic envelope wrapping for JSON output
20
+ * - Consistent error handling
21
+ * - Execution time tracking
22
+ * - Version metadata injection
23
+ * - Stdout/stderr separation
24
+ */
25
+ class BaseCommand extends core_1.Command {
26
+ constructor() {
27
+ super(...arguments);
28
+ this.shouldUseEnvelope = false;
29
+ }
30
+ /**
31
+ * Initialize command and create envelope formatter
32
+ */
33
+ async init() {
34
+ var _a;
35
+ await super.init();
36
+ // Initialize disk cache (load from disk)
37
+ const diskCacheEnabled = process.env.NOTION_CLI_DISK_CACHE_ENABLED !== 'false';
38
+ if (diskCacheEnabled) {
39
+ try {
40
+ await disk_cache_1.diskCacheManager.initialize();
41
+ }
42
+ catch (error) {
43
+ // Silently ignore disk cache initialization errors
44
+ if (process.env.DEBUG) {
45
+ console.error('Failed to initialize disk cache:', error);
46
+ }
47
+ }
48
+ }
49
+ // Get command name from ID (e.g., "page:retrieve" -> "page retrieve")
50
+ const commandName = ((_a = this.id) === null || _a === void 0 ? void 0 : _a.replace(/:/g, ' ')) || 'unknown';
51
+ // Get version from config
52
+ const version = this.config.version;
53
+ // Initialize envelope formatter
54
+ this.envelope = new envelope_1.EnvelopeFormatter(commandName, version);
55
+ }
56
+ /**
57
+ * Cleanup hook - flushes disk cache and destroys HTTP agents before exit
58
+ */
59
+ async finally(error) {
60
+ // Destroy HTTP agents to close all connections
61
+ try {
62
+ (0, http_agent_1.destroyAgents)();
63
+ }
64
+ catch (agentError) {
65
+ // Silently ignore agent cleanup errors
66
+ if (process.env.DEBUG) {
67
+ console.error('Failed to destroy HTTP agents:', agentError);
68
+ }
69
+ }
70
+ // Flush disk cache before exit
71
+ const diskCacheEnabled = process.env.NOTION_CLI_DISK_CACHE_ENABLED !== 'false';
72
+ if (diskCacheEnabled) {
73
+ try {
74
+ await disk_cache_1.diskCacheManager.shutdown();
75
+ }
76
+ catch (shutdownError) {
77
+ // Silently ignore shutdown errors
78
+ if (process.env.DEBUG) {
79
+ console.error('Failed to shutdown disk cache:', shutdownError);
80
+ }
81
+ }
82
+ }
83
+ await super.finally(error);
84
+ }
85
+ /**
86
+ * Determine if envelope should be used based on flags
87
+ */
88
+ checkEnvelopeUsage(flags) {
89
+ return !!(flags.json || flags['compact-json']);
90
+ }
91
+ /**
92
+ * Output success response with automatic envelope wrapping
93
+ *
94
+ * @param data - Response data
95
+ * @param flags - Command flags
96
+ * @param additionalMetadata - Optional metadata to include
97
+ */
98
+ outputSuccess(data, flags, additionalMetadata) {
99
+ // Check if we should use envelope
100
+ this.shouldUseEnvelope = this.checkEnvelopeUsage(flags);
101
+ if (this.shouldUseEnvelope) {
102
+ const envelope = this.envelope.wrapSuccess(data, additionalMetadata);
103
+ this.envelope.outputEnvelope(envelope, flags, this.log.bind(this));
104
+ process.exit(this.envelope.getExitCode(envelope));
105
+ }
106
+ else {
107
+ // Non-envelope output (table, markdown, etc.) - handled by caller
108
+ // This path should not normally be reached as caller handles non-JSON output
109
+ throw new Error('outputSuccess should only be called for JSON output');
110
+ }
111
+ }
112
+ /**
113
+ * Output error response with automatic envelope wrapping
114
+ *
115
+ * @param error - Error object
116
+ * @param flags - Command flags
117
+ * @param additionalContext - Optional error context
118
+ */
119
+ outputError(error, flags, additionalContext) {
120
+ // Wrap raw errors in NotionCLIError
121
+ const cliError = error instanceof index_1.NotionCLIError ? error : (0, index_1.wrapNotionError)(error);
122
+ // Check if we should use envelope
123
+ this.shouldUseEnvelope = this.checkEnvelopeUsage(flags);
124
+ if (this.shouldUseEnvelope) {
125
+ const envelope = this.envelope.wrapError(cliError, additionalContext);
126
+ this.envelope.outputEnvelope(envelope, flags, this.log.bind(this));
127
+ process.exit(this.envelope.getExitCode(envelope));
128
+ }
129
+ else {
130
+ // Non-JSON mode - use oclif's error handling
131
+ this.error(cliError.message, { exit: this.getExitCodeForError(cliError) });
132
+ }
133
+ }
134
+ /**
135
+ * Get appropriate exit code for error
136
+ */
137
+ getExitCodeForError(error) {
138
+ // CLI validation errors
139
+ if (error.code === 'VALIDATION_ERROR') {
140
+ return envelope_1.ExitCode.CLI_ERROR;
141
+ }
142
+ // API errors (default)
143
+ return envelope_1.ExitCode.API_ERROR;
144
+ }
145
+ /**
146
+ * Catch handler that ensures proper envelope error output
147
+ */
148
+ async catch(error) {
149
+ // If command has already handled the error via outputError, just propagate
150
+ if (error.exitCode !== undefined) {
151
+ throw error;
152
+ }
153
+ // Otherwise, wrap and handle the error
154
+ const cliError = (0, index_1.wrapNotionError)(error);
155
+ this.error(cliError.message, { exit: this.getExitCodeForError(cliError) });
156
+ }
157
+ }
158
+ exports.BaseCommand = BaseCommand;
159
+ /**
160
+ * Standard flags that all envelope-enabled commands should include
161
+ */
162
+ exports.EnvelopeFlags = {
163
+ json: core_1.Flags.boolean({
164
+ char: 'j',
165
+ description: 'Output as JSON envelope (recommended for automation)',
166
+ default: false,
167
+ }),
168
+ 'compact-json': core_1.Flags.boolean({
169
+ char: 'c',
170
+ description: 'Output as compact JSON envelope (single-line, ideal for piping)',
171
+ default: false,
172
+ exclusive: ['markdown', 'pretty'],
173
+ }),
174
+ raw: core_1.Flags.boolean({
175
+ char: 'r',
176
+ description: 'Output raw API response without envelope (legacy mode)',
177
+ default: false,
178
+ }),
179
+ };
@@ -0,0 +1,14 @@
1
+ export declare const AutomationFlags: {
2
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
3
+ 'page-size': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
4
+ retry: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
5
+ timeout: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
6
+ 'no-cache': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ minimal: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ };
10
+ export declare const OutputFormatFlags: {
11
+ markdown: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
12
+ 'compact-json': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
+ pretty: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
14
+ };
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OutputFormatFlags = exports.AutomationFlags = void 0;
4
+ const core_1 = require("@oclif/core");
5
+ exports.AutomationFlags = {
6
+ json: core_1.Flags.boolean({
7
+ char: 'j',
8
+ description: 'Output as JSON (recommended for automation)',
9
+ default: false,
10
+ }),
11
+ 'page-size': core_1.Flags.integer({
12
+ description: 'Items per page (1-100, default: 100 for automation)',
13
+ min: 1,
14
+ max: 100,
15
+ default: 100,
16
+ }),
17
+ retry: core_1.Flags.boolean({
18
+ description: 'Auto-retry on rate limit (respects Retry-After header)',
19
+ default: true,
20
+ }),
21
+ timeout: core_1.Flags.integer({
22
+ description: 'Request timeout in milliseconds',
23
+ default: 30000,
24
+ }),
25
+ 'no-cache': core_1.Flags.boolean({
26
+ description: 'Bypass cache and force fresh API calls',
27
+ default: false,
28
+ }),
29
+ verbose: core_1.Flags.boolean({
30
+ char: 'v',
31
+ description: 'Enable verbose logging to stderr (retry events, cache stats) - never pollutes stdout',
32
+ default: false,
33
+ env: 'NOTION_CLI_VERBOSE',
34
+ }),
35
+ minimal: core_1.Flags.boolean({
36
+ description: 'Strip unnecessary metadata (created_by, last_edited_by, object fields, request_id, etc.) - reduces response size by ~40%',
37
+ default: false,
38
+ }),
39
+ };
40
+ exports.OutputFormatFlags = {
41
+ markdown: core_1.Flags.boolean({
42
+ char: 'm',
43
+ description: 'Output as markdown table (GitHub-flavored)',
44
+ default: false,
45
+ exclusive: ['compact-json', 'pretty'],
46
+ }),
47
+ 'compact-json': core_1.Flags.boolean({
48
+ char: 'c',
49
+ description: 'Output as compact JSON (single-line, ideal for piping)',
50
+ default: false,
51
+ exclusive: ['markdown', 'pretty'],
52
+ }),
53
+ pretty: core_1.Flags.boolean({
54
+ char: 'P',
55
+ description: 'Output as pretty table with borders',
56
+ default: false,
57
+ exclusive: ['markdown', 'compact-json'],
58
+ }),
59
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Simple in-memory caching layer for Notion API responses
3
+ * Supports TTL (time-to-live) and cache invalidation
4
+ * Integrated with disk cache for persistence across CLI invocations
5
+ */
6
+ export interface CacheEntry<T> {
7
+ data: T;
8
+ timestamp: number;
9
+ ttl: number;
10
+ }
11
+ export interface CacheStats {
12
+ hits: number;
13
+ misses: number;
14
+ sets: number;
15
+ evictions: number;
16
+ size: number;
17
+ }
18
+ export interface CacheConfig {
19
+ enabled: boolean;
20
+ defaultTtl: number;
21
+ maxSize: number;
22
+ ttlByType: {
23
+ dataSource: number;
24
+ database: number;
25
+ user: number;
26
+ page: number;
27
+ block: number;
28
+ };
29
+ }
30
+ export declare class CacheManager {
31
+ private cache;
32
+ private stats;
33
+ private config;
34
+ constructor(config?: Partial<CacheConfig>);
35
+ /**
36
+ * Generate a cache key from resource type and identifiers
37
+ */
38
+ private generateKey;
39
+ /**
40
+ * Check if a cache entry is still valid
41
+ */
42
+ private isValid;
43
+ /**
44
+ * Evict expired entries
45
+ */
46
+ private evictExpired;
47
+ /**
48
+ * Evict oldest entries if cache is full
49
+ */
50
+ private evictOldest;
51
+ /**
52
+ * Get a value from cache (checks memory, then disk)
53
+ */
54
+ get<T>(type: string, ...identifiers: Array<string | number | object>): Promise<T | null>;
55
+ /**
56
+ * Set a value in cache with optional custom TTL (writes to memory and disk)
57
+ */
58
+ set<T>(type: string, data: T, customTtl?: number, ...identifiers: Array<string | number | object>): void;
59
+ /**
60
+ * Invalidate specific cache entries by type and optional identifiers
61
+ */
62
+ invalidate(type: string, ...identifiers: Array<string | number | object>): void;
63
+ /**
64
+ * Clear all cache entries (memory and disk)
65
+ */
66
+ clear(): void;
67
+ /**
68
+ * Get cache statistics
69
+ */
70
+ getStats(): CacheStats;
71
+ /**
72
+ * Get cache hit rate
73
+ */
74
+ getHitRate(): number;
75
+ /**
76
+ * Check if cache is enabled
77
+ */
78
+ isEnabled(): boolean;
79
+ /**
80
+ * Get current configuration
81
+ */
82
+ getConfig(): CacheConfig;
83
+ }
84
+ export declare const cacheManager: CacheManager;
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();