@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
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Notion ID Resolver
|
|
4
|
+
*
|
|
5
|
+
* Hybrid resolution system that supports:
|
|
6
|
+
* - URLs: https://www.notion.so/database-id
|
|
7
|
+
* - Direct IDs: database-id
|
|
8
|
+
* - Names: "Tasks Database" (via cache lookup and API fallback)
|
|
9
|
+
* - Smart database_id → data_source_id conversion
|
|
10
|
+
*
|
|
11
|
+
* Resolution stages:
|
|
12
|
+
* 1. URL extraction
|
|
13
|
+
* 2. Direct ID validation
|
|
14
|
+
* 3. Cache lookup (exact + aliases)
|
|
15
|
+
* 4. API search fallback
|
|
16
|
+
* 5. Smart database_id → data_source_id resolution (for databases)
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.resolveNotionId = resolveNotionId;
|
|
20
|
+
const notion_url_parser_1 = require("./notion-url-parser");
|
|
21
|
+
const errors_1 = require("../errors");
|
|
22
|
+
const workspace_cache_1 = require("./workspace-cache");
|
|
23
|
+
const notion_1 = require("../notion");
|
|
24
|
+
const client_1 = require("@notionhq/client");
|
|
25
|
+
/**
|
|
26
|
+
* Resolve Notion input (URL, ID, or name) to a clean Notion ID
|
|
27
|
+
*
|
|
28
|
+
* Supports URLs, IDs, and name-based lookups via cache and API search.
|
|
29
|
+
* For databases, automatically detects and converts database_id to data_source_id.
|
|
30
|
+
*
|
|
31
|
+
* @param input - Database/page name, ID, or URL
|
|
32
|
+
* @param type - Resource type (for better error messages)
|
|
33
|
+
* @returns Clean Notion ID (32 hex characters without dashes)
|
|
34
|
+
* @throws NotionCLIError if input cannot be resolved
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // URL
|
|
38
|
+
* await resolveNotionId('https://notion.so/1fb79d4c71bb8032b722c82305b63a00')
|
|
39
|
+
* // Returns: '1fb79d4c71bb8032b722c82305b63a00'
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* // Direct ID
|
|
43
|
+
* await resolveNotionId('1fb79d4c71bb8032b722c82305b63a00')
|
|
44
|
+
* // Returns: '1fb79d4c71bb8032b722c82305b63a00'
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Name (via cache or API)
|
|
48
|
+
* await resolveNotionId('Tasks Database', 'database')
|
|
49
|
+
* // Returns: '1fb79d4c71bb8032b722c82305b63a00'
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // database_id auto-conversion
|
|
53
|
+
* await resolveNotionId('abc123...', 'database')
|
|
54
|
+
* // If abc123 is a database_id, auto-resolves to data_source_id
|
|
55
|
+
*/
|
|
56
|
+
async function resolveNotionId(input, type = 'database') {
|
|
57
|
+
if (!input || typeof input !== 'string') {
|
|
58
|
+
throw new errors_1.NotionCLIError(errors_1.NotionCLIErrorCode.VALIDATION_ERROR, `Invalid input: expected a ${type} name, ID, or URL`, [], { resourceType: type, userInput: String(input) });
|
|
59
|
+
}
|
|
60
|
+
const trimmed = input.trim();
|
|
61
|
+
// Stage 1: URL extraction
|
|
62
|
+
if ((0, notion_url_parser_1.isNotionUrl)(trimmed)) {
|
|
63
|
+
try {
|
|
64
|
+
const extractedId = (0, notion_url_parser_1.extractNotionId)(trimmed);
|
|
65
|
+
// For databases, try smart resolution in case URL contains database_id
|
|
66
|
+
if (type === 'database') {
|
|
67
|
+
return await trySmartDatabaseResolution(extractedId);
|
|
68
|
+
}
|
|
69
|
+
return extractedId;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw errors_1.NotionCLIErrorFactory.invalidIdFormat(trimmed, type);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Stage 2: Direct ID validation
|
|
76
|
+
if (isValidNotionId(trimmed)) {
|
|
77
|
+
const extractedId = (0, notion_url_parser_1.extractNotionId)(trimmed);
|
|
78
|
+
// For databases, try smart resolution in case it's a database_id
|
|
79
|
+
if (type === 'database') {
|
|
80
|
+
return await trySmartDatabaseResolution(extractedId);
|
|
81
|
+
}
|
|
82
|
+
return extractedId;
|
|
83
|
+
}
|
|
84
|
+
// Stage 3: Cache lookup (exact + aliases)
|
|
85
|
+
const fromCache = await searchCache(trimmed);
|
|
86
|
+
if (fromCache)
|
|
87
|
+
return fromCache;
|
|
88
|
+
// Stage 4: API search as fallback
|
|
89
|
+
const fromApi = await searchNotionApi(trimmed, type);
|
|
90
|
+
if (fromApi)
|
|
91
|
+
return fromApi;
|
|
92
|
+
// Nothing found - throw helpful error
|
|
93
|
+
if (type === 'database') {
|
|
94
|
+
throw errors_1.NotionCLIErrorFactory.workspaceNotSynced(trimmed);
|
|
95
|
+
}
|
|
96
|
+
throw errors_1.NotionCLIErrorFactory.resourceNotFound(type, trimmed);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Smart database resolution: handles database_id → data_source_id conversion
|
|
100
|
+
*
|
|
101
|
+
* When a user provides a database_id (from parent.database_id field),
|
|
102
|
+
* this function detects the error and automatically resolves it to the
|
|
103
|
+
* correct data_source_id.
|
|
104
|
+
*
|
|
105
|
+
* @param databaseId - Potential database_id or data_source_id
|
|
106
|
+
* @returns data_source_id if valid, throws error otherwise
|
|
107
|
+
*/
|
|
108
|
+
async function trySmartDatabaseResolution(databaseId) {
|
|
109
|
+
try {
|
|
110
|
+
// Try direct lookup with data_source_id
|
|
111
|
+
await (0, notion_1.retrieveDataSource)(databaseId);
|
|
112
|
+
// If successful, it's a valid data_source_id
|
|
113
|
+
return databaseId;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
// Check if this is an object_not_found error (404)
|
|
117
|
+
const isNotFound = error.status === 404 ||
|
|
118
|
+
error.code === 'object_not_found' ||
|
|
119
|
+
(error.notionError && error.notionError.code === 'object_not_found');
|
|
120
|
+
if (isNotFound) {
|
|
121
|
+
// Try to resolve database_id → data_source_id
|
|
122
|
+
const dataSourceId = await resolveDatabaseIdToDataSourceId(databaseId);
|
|
123
|
+
if (dataSourceId) {
|
|
124
|
+
// Log helpful message about conversion
|
|
125
|
+
console.log(`\nInfo: Resolved database_id to data_source_id`);
|
|
126
|
+
console.log(` database_id: ${databaseId}`);
|
|
127
|
+
console.log(` data_source_id: ${dataSourceId}`);
|
|
128
|
+
console.log(`\nNote: Use data_source_id for database operations.`);
|
|
129
|
+
console.log(` The database_id from parent.database_id won't work directly.\n`);
|
|
130
|
+
return dataSourceId;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// If we can't resolve it, throw the original error
|
|
134
|
+
throw (0, errors_1.wrapNotionError)(error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Resolve database_id to data_source_id by searching for pages
|
|
139
|
+
*
|
|
140
|
+
* When a user provides a database_id (from parent.database_id field),
|
|
141
|
+
* we search for pages that have this database as their parent, and
|
|
142
|
+
* extract the data_source_id from the parent field.
|
|
143
|
+
*
|
|
144
|
+
* @param databaseId - The database_id to resolve
|
|
145
|
+
* @returns data_source_id if found, null otherwise
|
|
146
|
+
*/
|
|
147
|
+
async function resolveDatabaseIdToDataSourceId(databaseId) {
|
|
148
|
+
try {
|
|
149
|
+
// Search for pages with this database_id as parent
|
|
150
|
+
const response = await (0, notion_1.search)({
|
|
151
|
+
filter: {
|
|
152
|
+
property: 'object',
|
|
153
|
+
value: 'page'
|
|
154
|
+
},
|
|
155
|
+
page_size: 100 // Search more pages to increase chance of finding one
|
|
156
|
+
});
|
|
157
|
+
if (!response || !response.results || response.results.length === 0) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
// Look through results for a page with matching parent.database_id
|
|
161
|
+
for (const result of response.results) {
|
|
162
|
+
if (result.object !== 'page')
|
|
163
|
+
continue;
|
|
164
|
+
// Use type guard to ensure we have a full page with parent
|
|
165
|
+
if (!(0, client_1.isFullPage)(result))
|
|
166
|
+
continue;
|
|
167
|
+
// Check if parent type is database_id and matches our search
|
|
168
|
+
if (result.parent &&
|
|
169
|
+
result.parent.type === 'database_id' &&
|
|
170
|
+
result.parent.database_id === databaseId) {
|
|
171
|
+
// Extract data_source_id from the same parent object
|
|
172
|
+
// In the Notion API v5, pages have both database_id and data_source_id in parent
|
|
173
|
+
if ('data_source_id' in result.parent) {
|
|
174
|
+
return result.parent.data_source_id;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
// If search fails, return null and let the main error handling deal with it
|
|
182
|
+
if (process.env.DEBUG) {
|
|
183
|
+
console.error('Debug: Failed to resolve database_id to data_source_id:', error);
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Check if a string is a valid Notion ID (32 hex chars with optional dashes)
|
|
190
|
+
*/
|
|
191
|
+
function isValidNotionId(input) {
|
|
192
|
+
const cleaned = input.replace(/-/g, '');
|
|
193
|
+
return /^[a-f0-9]{32}$/i.test(cleaned);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Search cache for database/page by name
|
|
197
|
+
*
|
|
198
|
+
* Searches in this order:
|
|
199
|
+
* 1. Exact title match (case-insensitive)
|
|
200
|
+
* 2. Alias match (case-insensitive)
|
|
201
|
+
* 3. Partial title match (case-insensitive substring)
|
|
202
|
+
*
|
|
203
|
+
* @param query - Search query (database/page name)
|
|
204
|
+
* @returns Database/page ID if found, null otherwise
|
|
205
|
+
*/
|
|
206
|
+
async function searchCache(query) {
|
|
207
|
+
const cache = await (0, workspace_cache_1.loadCache)();
|
|
208
|
+
if (!cache)
|
|
209
|
+
return null;
|
|
210
|
+
const normalized = query.toLowerCase().trim();
|
|
211
|
+
// 1. Try exact title match
|
|
212
|
+
for (const db of cache.databases) {
|
|
213
|
+
if (db.titleNormalized === normalized) {
|
|
214
|
+
return db.id;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// 2. Try alias match
|
|
218
|
+
for (const db of cache.databases) {
|
|
219
|
+
if (db.aliases.includes(normalized)) {
|
|
220
|
+
return db.id;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// 3. Try partial match (substring in title)
|
|
224
|
+
for (const db of cache.databases) {
|
|
225
|
+
if (db.titleNormalized.includes(normalized)) {
|
|
226
|
+
return db.id;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Search Notion API for database/page by name
|
|
233
|
+
*
|
|
234
|
+
* Uses Notion's search API as a fallback when cache lookup fails.
|
|
235
|
+
*
|
|
236
|
+
* @param query - Search query (database/page name)
|
|
237
|
+
* @param type - Resource type ('database' or 'page')
|
|
238
|
+
* @returns Database/page ID if found, null otherwise
|
|
239
|
+
*/
|
|
240
|
+
async function searchNotionApi(query, type) {
|
|
241
|
+
try {
|
|
242
|
+
// Search Notion API
|
|
243
|
+
const response = await (0, notion_1.search)({
|
|
244
|
+
query,
|
|
245
|
+
filter: {
|
|
246
|
+
property: 'object',
|
|
247
|
+
value: type === 'database' ? 'data_source' : 'page'
|
|
248
|
+
},
|
|
249
|
+
page_size: 10
|
|
250
|
+
});
|
|
251
|
+
// Return first match
|
|
252
|
+
if (response && response.results && response.results.length > 0) {
|
|
253
|
+
return response.results[0].id;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// API search failed, return null
|
|
259
|
+
// The caller will throw a more helpful error message
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notion URL Parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts clean Notion IDs from various input formats:
|
|
5
|
+
* - Full URLs: https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00?v=...
|
|
6
|
+
* - Short URLs: notion.so/1fb79d4c71bb8032b722c82305b63a00
|
|
7
|
+
* - Raw IDs with dashes: 1fb79d4c-71bb-8032-b722-c82305b63a00
|
|
8
|
+
* - Raw IDs without dashes: 1fb79d4c71bb8032b722c82305b63a00
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Extract Notion ID from URL or raw ID
|
|
12
|
+
*
|
|
13
|
+
* @param input - Full Notion URL, partial URL, or raw ID
|
|
14
|
+
* @returns Clean Notion ID (32 hex characters without dashes)
|
|
15
|
+
* @throws Error if input is invalid
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Full URL
|
|
19
|
+
* extractNotionId('https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00?v=...')
|
|
20
|
+
* // Returns: '1fb79d4c71bb8032b722c82305b63a00'
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Raw ID with dashes
|
|
24
|
+
* extractNotionId('1fb79d4c-71bb-8032-b722-c82305b63a00')
|
|
25
|
+
* // Returns: '1fb79d4c71bb8032b722c82305b63a00'
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Already clean ID
|
|
29
|
+
* extractNotionId('1fb79d4c71bb8032b722c82305b63a00')
|
|
30
|
+
* // Returns: '1fb79d4c71bb8032b722c82305b63a00'
|
|
31
|
+
*/
|
|
32
|
+
export declare function extractNotionId(input: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Check if a string looks like a Notion URL
|
|
35
|
+
*
|
|
36
|
+
* @param input - String to check
|
|
37
|
+
* @returns True if input appears to be a Notion URL
|
|
38
|
+
*/
|
|
39
|
+
export declare function isNotionUrl(input: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Check if a string looks like a valid Notion ID
|
|
42
|
+
*
|
|
43
|
+
* @param input - String to check
|
|
44
|
+
* @returns True if input appears to be a valid Notion ID
|
|
45
|
+
*/
|
|
46
|
+
export declare function isValidNotionId(input: string): boolean;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Notion URL Parser
|
|
4
|
+
*
|
|
5
|
+
* Extracts clean Notion IDs from various input formats:
|
|
6
|
+
* - Full URLs: https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00?v=...
|
|
7
|
+
* - Short URLs: notion.so/1fb79d4c71bb8032b722c82305b63a00
|
|
8
|
+
* - Raw IDs with dashes: 1fb79d4c-71bb-8032-b722-c82305b63a00
|
|
9
|
+
* - Raw IDs without dashes: 1fb79d4c71bb8032b722c82305b63a00
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.extractNotionId = extractNotionId;
|
|
13
|
+
exports.isNotionUrl = isNotionUrl;
|
|
14
|
+
exports.isValidNotionId = isValidNotionId;
|
|
15
|
+
/**
|
|
16
|
+
* Extract Notion ID from URL or raw ID
|
|
17
|
+
*
|
|
18
|
+
* @param input - Full Notion URL, partial URL, or raw ID
|
|
19
|
+
* @returns Clean Notion ID (32 hex characters without dashes)
|
|
20
|
+
* @throws Error if input is invalid
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Full URL
|
|
24
|
+
* extractNotionId('https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00?v=...')
|
|
25
|
+
* // Returns: '1fb79d4c71bb8032b722c82305b63a00'
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Raw ID with dashes
|
|
29
|
+
* extractNotionId('1fb79d4c-71bb-8032-b722-c82305b63a00')
|
|
30
|
+
* // Returns: '1fb79d4c71bb8032b722c82305b63a00'
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Already clean ID
|
|
34
|
+
* extractNotionId('1fb79d4c71bb8032b722c82305b63a00')
|
|
35
|
+
* // Returns: '1fb79d4c71bb8032b722c82305b63a00'
|
|
36
|
+
*/
|
|
37
|
+
function extractNotionId(input) {
|
|
38
|
+
if (!input || typeof input !== 'string') {
|
|
39
|
+
throw new Error('Input must be a non-empty string');
|
|
40
|
+
}
|
|
41
|
+
const trimmed = input.trim();
|
|
42
|
+
// Check if it's a URL (contains notion.so or http)
|
|
43
|
+
if (trimmed.includes('notion.so') || trimmed.includes('http')) {
|
|
44
|
+
return extractIdFromUrl(trimmed);
|
|
45
|
+
}
|
|
46
|
+
// Not a URL, treat as raw ID
|
|
47
|
+
return cleanRawId(trimmed);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Extract ID from Notion URL
|
|
51
|
+
*/
|
|
52
|
+
function extractIdFromUrl(url) {
|
|
53
|
+
// Notion URL patterns:
|
|
54
|
+
// https://www.notion.so/{id}
|
|
55
|
+
// https://www.notion.so/{id}?v={view_id}
|
|
56
|
+
// https://notion.so/{id}
|
|
57
|
+
// www.notion.so/{id}
|
|
58
|
+
// Match notion.so/ followed by hex characters and optional dashes
|
|
59
|
+
const match = url.match(/notion\.so\/([a-f0-9-]{32,36})/i);
|
|
60
|
+
if (match) {
|
|
61
|
+
return cleanRawId(match[1]);
|
|
62
|
+
}
|
|
63
|
+
throw new Error(`Could not extract Notion ID from URL: ${url}\n\n` +
|
|
64
|
+
`Expected format: https://www.notion.so/{id}\n` +
|
|
65
|
+
`Example: https://www.notion.so/1fb79d4c71bb8032b722c82305b63a00`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Clean raw ID by removing dashes and validating format
|
|
69
|
+
*/
|
|
70
|
+
function cleanRawId(id) {
|
|
71
|
+
// Remove all dashes
|
|
72
|
+
const cleaned = id.replace(/-/g, '');
|
|
73
|
+
// Validate: must be exactly 32 hex characters
|
|
74
|
+
if (!/^[a-f0-9]{32}$/i.test(cleaned)) {
|
|
75
|
+
throw new Error(`Invalid Notion ID format: ${id}\n\n` +
|
|
76
|
+
`Expected: 32 hexadecimal characters (with or without dashes)\n` +
|
|
77
|
+
`Example: 1fb79d4c71bb8032b722c82305b63a00\n` +
|
|
78
|
+
`Example: 1fb79d4c-71bb-8032-b722-c82305b63a00`);
|
|
79
|
+
}
|
|
80
|
+
return cleaned.toLowerCase();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if a string looks like a Notion URL
|
|
84
|
+
*
|
|
85
|
+
* @param input - String to check
|
|
86
|
+
* @returns True if input appears to be a Notion URL
|
|
87
|
+
*/
|
|
88
|
+
function isNotionUrl(input) {
|
|
89
|
+
if (!input || typeof input !== 'string') {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return input.includes('notion.so');
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if a string looks like a valid Notion ID
|
|
96
|
+
*
|
|
97
|
+
* @param input - String to check
|
|
98
|
+
* @returns True if input appears to be a valid Notion ID
|
|
99
|
+
*/
|
|
100
|
+
function isValidNotionId(input) {
|
|
101
|
+
if (!input || typeof input !== 'string') {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
extractNotionId(input);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { GetDataSourceResponse } from '@notionhq/client/build/src/api-endpoints';
|
|
2
|
+
/**
|
|
3
|
+
* Simple flat property format for AI agents
|
|
4
|
+
* Instead of complex Notion nested structures, use simple key-value pairs:
|
|
5
|
+
* { "Name": "Task", "Status": "Done", "Tags": ["urgent", "bug"] }
|
|
6
|
+
*/
|
|
7
|
+
export interface SimpleProperties {
|
|
8
|
+
[key: string]: string | number | boolean | string[] | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Notion API property format (deeply nested)
|
|
12
|
+
*/
|
|
13
|
+
export interface NotionProperties {
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Expand simple flat properties to Notion API format
|
|
18
|
+
*
|
|
19
|
+
* This function takes simplified property values and automatically expands them
|
|
20
|
+
* to the correct Notion API structure based on the database schema.
|
|
21
|
+
*
|
|
22
|
+
* @param simple - Flat key-value property object
|
|
23
|
+
* @param schema - Database properties schema from data source
|
|
24
|
+
* @returns Properly formatted Notion properties object
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Input (simple):
|
|
28
|
+
* { "Name": "My Task", "Status": "In Progress", "Priority": 5 }
|
|
29
|
+
*
|
|
30
|
+
* // Output (Notion format):
|
|
31
|
+
* {
|
|
32
|
+
* "Name": { "title": [{ "text": { "content": "My Task" } }] },
|
|
33
|
+
* "Status": { "select": { "name": "In Progress" } },
|
|
34
|
+
* "Priority": { "number": 5 }
|
|
35
|
+
* }
|
|
36
|
+
*/
|
|
37
|
+
export declare function expandSimpleProperties(simple: SimpleProperties, schema: GetDataSourceResponse['properties']): Promise<NotionProperties>;
|
|
38
|
+
/**
|
|
39
|
+
* Validate simple properties against schema before expansion
|
|
40
|
+
* This can be called optionally before expandSimpleProperties to get detailed errors
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateSimpleProperties(simple: SimpleProperties, schema: GetDataSourceResponse['properties']): {
|
|
43
|
+
valid: boolean;
|
|
44
|
+
errors: string[];
|
|
45
|
+
};
|