n8n-nodes-onedrive-business 1.0.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.
@@ -0,0 +1,91 @@
1
+ import type { FileStabilityInfo, TriggerEvent } from '../types';
2
+ import { GraphClient } from './GraphClient';
3
+ import { StateStore } from './StateStore';
4
+ /**
5
+ * Delta Query Processor for OneDrive Business
6
+ *
7
+ * This is the CORE deduplication engine that implements:
8
+ *
9
+ * 1. File Stability Checking
10
+ * - Ensures files have completed uploading before processing
11
+ * - Validates quickXorHash presence
12
+ * - Checks timestamp alignment
13
+ *
14
+ * 2. Version-based Deduplication
15
+ * - Tracks ${itemId}_${eTag} to identify unique versions
16
+ * - Prevents multiple executions for the same file version
17
+ *
18
+ * 3. Stability Window
19
+ * - Waits 15 seconds for files still being modified
20
+ * - Prevents processing incomplete uploads
21
+ *
22
+ * 4. Event Classification
23
+ * - Distinguishes 'created' vs 'updated' using eTag comparison
24
+ * - Provides accurate event types to workflows
25
+ *
26
+ * WHY THIS IS NECESSARY:
27
+ * SharePoint-backed OneDrive generates multiple updates per upload:
28
+ * - File created (size=0)
29
+ * - Metadata updated
30
+ * - Content uploaded
31
+ * - Final metadata sync
32
+ *
33
+ * Without this processor, ONE file upload would trigger FOUR workflow executions.
34
+ */
35
+ export declare class DeltaProcessor {
36
+ private graphClient;
37
+ private stateStore;
38
+ private drivePath;
39
+ private stabilityTracker;
40
+ private readonly STABILITY_WINDOW_MS;
41
+ constructor(graphClient: GraphClient, stateStore: StateStore, drivePath: string);
42
+ /**
43
+ * Process delta query and return triggerable events
44
+ *
45
+ * This is the main entry point called by the trigger node
46
+ */
47
+ processDeltaQuery(eventFilter: Set<string>): Promise<TriggerEvent[]>;
48
+ /**
49
+ * Fetch all pages from delta query
50
+ * Handles @odata.nextLink pagination and stores @odata.deltaLink
51
+ */
52
+ private fetchAllDeltaPages;
53
+ /**
54
+ * Process a single delta item
55
+ * Returns a TriggerEvent if the item should trigger a workflow
56
+ */
57
+ private processItem;
58
+ /**
59
+ * Classify event as created vs updated
60
+ *
61
+ * Logic:
62
+ * - If item NOT in lastKnownItems → created
63
+ * - If item eTag differs from lastKnownItems → updated
64
+ */
65
+ private classifyEvent;
66
+ /**
67
+ * Check if a file is stable and ready to be processed
68
+ *
69
+ * A file is considered stable when:
70
+ * 1. Size > 0 (not empty placeholder)
71
+ * 2. quickXorHash is present (upload complete)
72
+ * 3. lastModifiedDateTime matches fileSystemInfo.lastModifiedDateTime
73
+ * 4. At least 15 seconds have passed since last modification
74
+ *
75
+ * Returns true if stable, false if still being uploaded
76
+ */
77
+ private checkFileStability;
78
+ /**
79
+ * Track file stability over time
80
+ * Helps debug upload issues
81
+ */
82
+ private trackFileStability;
83
+ /**
84
+ * Get stability information for debugging
85
+ */
86
+ getStabilityInfo(itemId: string): FileStabilityInfo | undefined;
87
+ /**
88
+ * Clean up resources
89
+ */
90
+ dispose(): void;
91
+ }
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DeltaProcessor = void 0;
4
+ const helpers_1 = require("./helpers");
5
+ /**
6
+ * Delta Query Processor for OneDrive Business
7
+ *
8
+ * This is the CORE deduplication engine that implements:
9
+ *
10
+ * 1. File Stability Checking
11
+ * - Ensures files have completed uploading before processing
12
+ * - Validates quickXorHash presence
13
+ * - Checks timestamp alignment
14
+ *
15
+ * 2. Version-based Deduplication
16
+ * - Tracks ${itemId}_${eTag} to identify unique versions
17
+ * - Prevents multiple executions for the same file version
18
+ *
19
+ * 3. Stability Window
20
+ * - Waits 15 seconds for files still being modified
21
+ * - Prevents processing incomplete uploads
22
+ *
23
+ * 4. Event Classification
24
+ * - Distinguishes 'created' vs 'updated' using eTag comparison
25
+ * - Provides accurate event types to workflows
26
+ *
27
+ * WHY THIS IS NECESSARY:
28
+ * SharePoint-backed OneDrive generates multiple updates per upload:
29
+ * - File created (size=0)
30
+ * - Metadata updated
31
+ * - Content uploaded
32
+ * - Final metadata sync
33
+ *
34
+ * Without this processor, ONE file upload would trigger FOUR workflow executions.
35
+ */
36
+ class DeltaProcessor {
37
+ constructor(graphClient, stateStore, drivePath) {
38
+ // Stability tracking for files currently being uploaded
39
+ this.stabilityTracker = new Map();
40
+ // Stability window: wait 15 seconds after last modification
41
+ this.STABILITY_WINDOW_MS = 15000;
42
+ this.graphClient = graphClient;
43
+ this.stateStore = stateStore;
44
+ this.drivePath = drivePath;
45
+ }
46
+ /**
47
+ * Process delta query and return triggerable events
48
+ *
49
+ * This is the main entry point called by the trigger node
50
+ */
51
+ async processDeltaQuery(eventFilter // e.g., ['file.created', 'file.updated']
52
+ ) {
53
+ const events = [];
54
+ try {
55
+ // Get current state
56
+ const state = this.stateStore.getState();
57
+ // Determine the delta endpoint
58
+ let deltaEndpoint;
59
+ if (state.deltaLink) {
60
+ // Use existing delta link (contains the full URL)
61
+ // Extract the path after /v1.0
62
+ const deltaUrl = new URL(state.deltaLink);
63
+ deltaEndpoint = deltaUrl.pathname.replace('/v1.0', '') + deltaUrl.search;
64
+ }
65
+ else {
66
+ // Initial delta query
67
+ deltaEndpoint = `${this.drivePath}/root/delta`;
68
+ }
69
+ // Fetch delta changes (handle pagination)
70
+ const items = await this.fetchAllDeltaPages(deltaEndpoint);
71
+ // Process each item
72
+ for (const item of items) {
73
+ const event = await this.processItem(item, eventFilter);
74
+ if (event) {
75
+ events.push(event);
76
+ }
77
+ }
78
+ return events;
79
+ }
80
+ catch (error) {
81
+ console.error('Error processing delta query:', error);
82
+ throw error;
83
+ }
84
+ }
85
+ /**
86
+ * Fetch all pages from delta query
87
+ * Handles @odata.nextLink pagination and stores @odata.deltaLink
88
+ */
89
+ async fetchAllDeltaPages(initialEndpoint) {
90
+ const allItems = [];
91
+ let currentEndpoint = initialEndpoint;
92
+ while (currentEndpoint) {
93
+ const response = await this.graphClient.get(currentEndpoint);
94
+ allItems.push(...response.value);
95
+ // Check for next page
96
+ if (response['@odata.nextLink']) {
97
+ // Extract path from full URL
98
+ const url = new URL(response['@odata.nextLink']);
99
+ currentEndpoint = url.pathname.replace('/v1.0', '') + url.search;
100
+ }
101
+ else if (response['@odata.deltaLink']) {
102
+ // Save delta link for next run
103
+ await this.stateStore.updateDeltaLink(response['@odata.deltaLink']);
104
+ currentEndpoint = '';
105
+ }
106
+ else {
107
+ // No more pages
108
+ currentEndpoint = '';
109
+ }
110
+ }
111
+ return allItems;
112
+ }
113
+ /**
114
+ * Process a single delta item
115
+ * Returns a TriggerEvent if the item should trigger a workflow
116
+ */
117
+ async processItem(item, eventFilter) {
118
+ // Skip deleted items (unless specifically requested)
119
+ if (item.deleted) {
120
+ return null;
121
+ }
122
+ // Skip root folder
123
+ if (!item.parentReference) {
124
+ return null;
125
+ }
126
+ // Determine event type
127
+ const eventType = this.classifyEvent(item);
128
+ // Check if this event type is enabled
129
+ if (!eventFilter.has(eventType)) {
130
+ return null;
131
+ }
132
+ // Check if already processed (deduplication)
133
+ const versionKey = (0, helpers_1.buildVersionKey)(item.id, item.eTag);
134
+ if (this.stateStore.isVersionProcessed(item.id, item.eTag)) {
135
+ // Already processed this version
136
+ return null;
137
+ }
138
+ // For files, check stability
139
+ if ((0, helpers_1.isFile)(item)) {
140
+ const isStable = await this.checkFileStability(item);
141
+ if (!isStable) {
142
+ // File is still being uploaded/modified, skip for now
143
+ return null;
144
+ }
145
+ }
146
+ // Mark as processed
147
+ await this.stateStore.markVersionProcessed(item.id, item.eTag);
148
+ // Update last known state
149
+ await this.stateStore.updateLastKnownItem(item.id, item.eTag, item.lastModifiedDateTime);
150
+ // Return the event
151
+ return {
152
+ eventType,
153
+ item,
154
+ timestamp: new Date().toISOString(),
155
+ };
156
+ }
157
+ /**
158
+ * Classify event as created vs updated
159
+ *
160
+ * Logic:
161
+ * - If item NOT in lastKnownItems → created
162
+ * - If item eTag differs from lastKnownItems → updated
163
+ */
164
+ classifyEvent(item) {
165
+ const lastKnown = this.stateStore.getLastKnownItem(item.id);
166
+ const isFileItem = (0, helpers_1.isFile)(item);
167
+ const isFolderItem = (0, helpers_1.isFolder)(item);
168
+ if (!lastKnown) {
169
+ // New item
170
+ if (isFileItem) {
171
+ return 'file.created';
172
+ }
173
+ else if (isFolderItem) {
174
+ return 'folder.created';
175
+ }
176
+ }
177
+ else {
178
+ // Existing item
179
+ if (lastKnown.eTag !== item.eTag) {
180
+ if (isFileItem) {
181
+ return 'file.updated';
182
+ }
183
+ else if (isFolderItem) {
184
+ return 'folder.updated';
185
+ }
186
+ }
187
+ }
188
+ // Default to updated
189
+ return isFileItem ? 'file.updated' : 'folder.updated';
190
+ }
191
+ /**
192
+ * Check if a file is stable and ready to be processed
193
+ *
194
+ * A file is considered stable when:
195
+ * 1. Size > 0 (not empty placeholder)
196
+ * 2. quickXorHash is present (upload complete)
197
+ * 3. lastModifiedDateTime matches fileSystemInfo.lastModifiedDateTime
198
+ * 4. At least 15 seconds have passed since last modification
199
+ *
200
+ * Returns true if stable, false if still being uploaded
201
+ */
202
+ async checkFileStability(item) {
203
+ var _a, _b, _c;
204
+ // Basic stability checks
205
+ const hasSize = item.size > 0;
206
+ const hasHash = !!((_b = (_a = item.file) === null || _a === void 0 ? void 0 : _a.hashes) === null || _b === void 0 ? void 0 : _b.quickXorHash);
207
+ const timestampsAlign = item.lastModifiedDateTime === ((_c = item.fileSystemInfo) === null || _c === void 0 ? void 0 : _c.lastModifiedDateTime);
208
+ const basicStability = hasSize && hasHash && timestampsAlign;
209
+ if (!basicStability) {
210
+ // Track this file for stability checking
211
+ this.trackFileStability(item, false);
212
+ return false;
213
+ }
214
+ // Check stability window (15 seconds)
215
+ const lastModified = new Date(item.lastModifiedDateTime).getTime();
216
+ const now = Date.now();
217
+ const timeSinceModification = now - lastModified;
218
+ if (timeSinceModification < this.STABILITY_WINDOW_MS) {
219
+ // File was modified recently, wait longer
220
+ this.trackFileStability(item, false);
221
+ return false;
222
+ }
223
+ // File is stable
224
+ this.trackFileStability(item, true);
225
+ return true;
226
+ }
227
+ /**
228
+ * Track file stability over time
229
+ * Helps debug upload issues
230
+ */
231
+ trackFileStability(item, isStable) {
232
+ var _a, _b;
233
+ const now = Date.now();
234
+ const existing = this.stabilityTracker.get(item.id);
235
+ const stabilityInfo = {
236
+ itemId: item.id,
237
+ eTag: item.eTag,
238
+ size: item.size,
239
+ lastModifiedDateTime: item.lastModifiedDateTime,
240
+ hasHash: !!((_b = (_a = item.file) === null || _a === void 0 ? void 0 : _a.hashes) === null || _b === void 0 ? void 0 : _b.quickXorHash),
241
+ isStable,
242
+ firstSeen: (existing === null || existing === void 0 ? void 0 : existing.firstSeen) || now,
243
+ lastChecked: now,
244
+ };
245
+ this.stabilityTracker.set(item.id, stabilityInfo);
246
+ // Clean up old entries (keep only last hour)
247
+ const oneHourAgo = now - 3600000;
248
+ for (const [itemId, info] of this.stabilityTracker.entries()) {
249
+ if (info.lastChecked < oneHourAgo) {
250
+ this.stabilityTracker.delete(itemId);
251
+ }
252
+ }
253
+ }
254
+ /**
255
+ * Get stability information for debugging
256
+ */
257
+ getStabilityInfo(itemId) {
258
+ return this.stabilityTracker.get(itemId);
259
+ }
260
+ /**
261
+ * Clean up resources
262
+ */
263
+ dispose() {
264
+ this.stabilityTracker.clear();
265
+ }
266
+ }
267
+ exports.DeltaProcessor = DeltaProcessor;
@@ -0,0 +1,67 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { IExecuteFunctions, IHookFunctions, IPollFunctions } from 'n8n-workflow';
4
+ import type { DriveItem, RetryConfig } from '../types';
5
+ /**
6
+ * Microsoft Graph API Client
7
+ *
8
+ * Centralized client for all Microsoft Graph API interactions.
9
+ * Handles:
10
+ * - Authentication (OAuth2 token management)
11
+ * - Retry logic with exponential backoff
12
+ * - 429 Throttling handling
13
+ * - Error normalization
14
+ * - Request/response type safety
15
+ */
16
+ export declare class GraphClient {
17
+ private baseUrl;
18
+ private context;
19
+ private retryConfig;
20
+ constructor(context: IExecuteFunctions | IHookFunctions | IPollFunctions, retryConfig?: Partial<RetryConfig>);
21
+ /**
22
+ * Make a GET request to Microsoft Graph API
23
+ */
24
+ get<T = any>(endpoint: string, queryParameters?: Record<string, any>): Promise<T>;
25
+ /**
26
+ * Make a POST request to Microsoft Graph API
27
+ */
28
+ post<T = any>(endpoint: string, body?: any, queryParameters?: Record<string, any>): Promise<T>;
29
+ /**
30
+ * Make a PATCH request to Microsoft Graph API
31
+ */
32
+ patch<T = any>(endpoint: string, body?: any, queryParameters?: Record<string, any>): Promise<T>;
33
+ /**
34
+ * Make a PUT request to Microsoft Graph API
35
+ */
36
+ put<T = any>(endpoint: string, body?: any, queryParameters?: Record<string, any>): Promise<T>;
37
+ /**
38
+ * Make a DELETE request to Microsoft Graph API
39
+ */
40
+ delete<T = any>(endpoint: string, queryParameters?: Record<string, any>): Promise<T>;
41
+ /**
42
+ * Download binary content from Microsoft Graph
43
+ */
44
+ downloadBinary(endpoint: string): Promise<Buffer>;
45
+ /**
46
+ * Upload binary content to Microsoft Graph
47
+ */
48
+ uploadBinary(endpoint: string, binaryData: Buffer, contentType?: string): Promise<DriveItem>;
49
+ /**
50
+ * Fetch all pages from a paginated endpoint
51
+ * Automatically follows @odata.nextLink
52
+ */
53
+ getAllPages<T>(endpoint: string, queryParameters?: Record<string, any>): Promise<T[]>;
54
+ /**
55
+ * Core request method with retry logic
56
+ */
57
+ private request;
58
+ /**
59
+ * Execute request with exponential backoff retry logic
60
+ * Handles 429 throttling and transient errors
61
+ */
62
+ private executeWithRetry;
63
+ /**
64
+ * Normalize errors to NodeApiError
65
+ */
66
+ private handleError;
67
+ }
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GraphClient = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const helpers_1 = require("./helpers");
6
+ /**
7
+ * Microsoft Graph API Client
8
+ *
9
+ * Centralized client for all Microsoft Graph API interactions.
10
+ * Handles:
11
+ * - Authentication (OAuth2 token management)
12
+ * - Retry logic with exponential backoff
13
+ * - 429 Throttling handling
14
+ * - Error normalization
15
+ * - Request/response type safety
16
+ */
17
+ class GraphClient {
18
+ constructor(context, retryConfig) {
19
+ var _a, _b, _c, _d;
20
+ this.baseUrl = 'https://graph.microsoft.com/v1.0';
21
+ this.context = context;
22
+ this.retryConfig = {
23
+ maxRetries: (_a = retryConfig === null || retryConfig === void 0 ? void 0 : retryConfig.maxRetries) !== null && _a !== void 0 ? _a : 3,
24
+ initialDelayMs: (_b = retryConfig === null || retryConfig === void 0 ? void 0 : retryConfig.initialDelayMs) !== null && _b !== void 0 ? _b : 1000,
25
+ maxDelayMs: (_c = retryConfig === null || retryConfig === void 0 ? void 0 : retryConfig.maxDelayMs) !== null && _c !== void 0 ? _c : 60000,
26
+ backoffMultiplier: (_d = retryConfig === null || retryConfig === void 0 ? void 0 : retryConfig.backoffMultiplier) !== null && _d !== void 0 ? _d : 2,
27
+ };
28
+ }
29
+ /**
30
+ * Make a GET request to Microsoft Graph API
31
+ */
32
+ async get(endpoint, queryParameters) {
33
+ return this.request('GET', endpoint, undefined, queryParameters);
34
+ }
35
+ /**
36
+ * Make a POST request to Microsoft Graph API
37
+ */
38
+ async post(endpoint, body, queryParameters) {
39
+ return this.request('POST', endpoint, body, queryParameters);
40
+ }
41
+ /**
42
+ * Make a PATCH request to Microsoft Graph API
43
+ */
44
+ async patch(endpoint, body, queryParameters) {
45
+ return this.request('PATCH', endpoint, body, queryParameters);
46
+ }
47
+ /**
48
+ * Make a PUT request to Microsoft Graph API
49
+ */
50
+ async put(endpoint, body, queryParameters) {
51
+ return this.request('PUT', endpoint, body, queryParameters);
52
+ }
53
+ /**
54
+ * Make a DELETE request to Microsoft Graph API
55
+ */
56
+ async delete(endpoint, queryParameters) {
57
+ return this.request('DELETE', endpoint, undefined, queryParameters);
58
+ }
59
+ /**
60
+ * Download binary content from Microsoft Graph
61
+ */
62
+ async downloadBinary(endpoint) {
63
+ const options = {
64
+ method: 'GET',
65
+ url: `${this.baseUrl}${endpoint}`,
66
+ encoding: null, // Return as Buffer
67
+ json: false,
68
+ };
69
+ try {
70
+ const response = await this.executeWithRetry(options);
71
+ return Buffer.from(response);
72
+ }
73
+ catch (error) {
74
+ throw this.handleError(error, 'GET', endpoint);
75
+ }
76
+ }
77
+ /**
78
+ * Upload binary content to Microsoft Graph
79
+ */
80
+ async uploadBinary(endpoint, binaryData, contentType = 'application/octet-stream') {
81
+ const options = {
82
+ method: 'PUT',
83
+ url: `${this.baseUrl}${endpoint}`,
84
+ body: binaryData,
85
+ headers: {
86
+ 'Content-Type': contentType,
87
+ },
88
+ json: false,
89
+ };
90
+ try {
91
+ const response = await this.executeWithRetry(options);
92
+ // Parse the JSON response
93
+ return typeof response === 'string' ? JSON.parse(response) : response;
94
+ }
95
+ catch (error) {
96
+ throw this.handleError(error, 'PUT', endpoint);
97
+ }
98
+ }
99
+ /**
100
+ * Fetch all pages from a paginated endpoint
101
+ * Automatically follows @odata.nextLink
102
+ */
103
+ async getAllPages(endpoint, queryParameters) {
104
+ const allItems = [];
105
+ let nextLink = endpoint;
106
+ while (nextLink) {
107
+ const response = await this.get(nextLink,
108
+ // Only send query parameters on the first request
109
+ nextLink === endpoint ? queryParameters : undefined);
110
+ allItems.push(...response.value);
111
+ nextLink = response['@odata.nextLink'];
112
+ }
113
+ return allItems;
114
+ }
115
+ /**
116
+ * Core request method with retry logic
117
+ */
118
+ async request(method, endpoint, body, queryParameters) {
119
+ const options = {
120
+ method,
121
+ url: `${this.baseUrl}${endpoint}`,
122
+ json: true,
123
+ };
124
+ if (body !== undefined) {
125
+ options.body = body;
126
+ }
127
+ if (queryParameters) {
128
+ options.qs = queryParameters;
129
+ }
130
+ try {
131
+ return await this.executeWithRetry(options);
132
+ }
133
+ catch (error) {
134
+ throw this.handleError(error, method, endpoint);
135
+ }
136
+ }
137
+ /**
138
+ * Execute request with exponential backoff retry logic
139
+ * Handles 429 throttling and transient errors
140
+ */
141
+ async executeWithRetry(options, retryCount = 0) {
142
+ var _a, _b;
143
+ try {
144
+ return await this.context.helpers.requestOAuth2.call(this.context, 'oneDriveBusinessOAuth2Api', options);
145
+ }
146
+ catch (error) {
147
+ const statusCode = error.statusCode || ((_a = error.response) === null || _a === void 0 ? void 0 : _a.statusCode);
148
+ // Handle 429 Too Many Requests (Throttling)
149
+ if (statusCode === 429) {
150
+ if (retryCount < this.retryConfig.maxRetries) {
151
+ const retryAfter = (0, helpers_1.parseRetryAfter)((_b = error.response) === null || _b === void 0 ? void 0 : _b.headers['retry-after']);
152
+ await (0, helpers_1.sleep)(retryAfter);
153
+ return this.executeWithRetry(options, retryCount + 1);
154
+ }
155
+ }
156
+ // Handle transient errors (5xx)
157
+ if (statusCode >= 500 && statusCode < 600) {
158
+ if (retryCount < this.retryConfig.maxRetries) {
159
+ const delay = (0, helpers_1.calculateBackoffDelay)(retryCount, this.retryConfig.initialDelayMs, this.retryConfig.maxDelayMs, this.retryConfig.backoffMultiplier);
160
+ await (0, helpers_1.sleep)(delay);
161
+ return this.executeWithRetry(options, retryCount + 1);
162
+ }
163
+ }
164
+ // No retry for other errors
165
+ throw error;
166
+ }
167
+ }
168
+ /**
169
+ * Normalize errors to NodeApiError
170
+ */
171
+ handleError(error, method, endpoint) {
172
+ var _a;
173
+ const parsedError = (0, helpers_1.parseGraphError)(error.error || error);
174
+ const errorMessage = `Microsoft Graph API Error [${method} ${endpoint}]: ${parsedError.code} - ${parsedError.message}`;
175
+ return new n8n_workflow_1.NodeApiError(this.context.getNode(), error, {
176
+ message: errorMessage,
177
+ description: parsedError.message,
178
+ httpCode: (_a = error.statusCode) === null || _a === void 0 ? void 0 : _a.toString(),
179
+ });
180
+ }
181
+ }
182
+ exports.GraphClient = GraphClient;
@@ -0,0 +1,96 @@
1
+ import type { NodeState } from '../types';
2
+ /**
3
+ * State Store for OneDrive Business Trigger Node
4
+ *
5
+ * Provides persistent storage for:
6
+ * - Delta query links
7
+ * - Processed file versions (deduplication)
8
+ * - Last known item states (for event classification)
9
+ * - Webhook subscription information
10
+ *
11
+ * State MUST survive n8n restarts and redeployments to prevent:
12
+ * - Duplicate workflow executions
13
+ * - Re-processing old changes
14
+ * - Losing track of processed items
15
+ */
16
+ export declare class StateStore {
17
+ private stateFilePath;
18
+ private nodeId;
19
+ private state;
20
+ private saveDebounceTimer;
21
+ constructor(nodeId: string, storageDir?: string);
22
+ /**
23
+ * Initialize and load state from disk
24
+ * Creates default state if file doesn't exist
25
+ */
26
+ initialize(tenantId: string, driveId: string, userId?: string, siteId?: string): Promise<NodeState>;
27
+ /**
28
+ * Get current state
29
+ * Throws if not initialized
30
+ */
31
+ getState(): NodeState;
32
+ /**
33
+ * Update delta link
34
+ */
35
+ updateDeltaLink(deltaLink: string): Promise<void>;
36
+ /**
37
+ * Mark a file version as processed
38
+ * Returns true if this version was NOT already processed
39
+ */
40
+ markVersionProcessed(itemId: string, eTag: string): Promise<boolean>;
41
+ /**
42
+ * Check if a version has been processed
43
+ */
44
+ isVersionProcessed(itemId: string, eTag: string): boolean;
45
+ /**
46
+ * Update last known state of an item
47
+ * Used for event classification (created vs updated)
48
+ */
49
+ updateLastKnownItem(itemId: string, eTag: string, lastModifiedDateTime: string): Promise<void>;
50
+ /**
51
+ * Get last known state of an item
52
+ */
53
+ getLastKnownItem(itemId: string): {
54
+ eTag: string;
55
+ lastModifiedDateTime: string;
56
+ } | null;
57
+ /**
58
+ * Update webhook subscription information
59
+ */
60
+ updateSubscription(subscriptionId: string, expirationTimestamp: number): Promise<void>;
61
+ /**
62
+ * Clear webhook subscription information
63
+ */
64
+ clearSubscription(): Promise<void>;
65
+ /**
66
+ * Clean up old processed versions to prevent unbounded growth
67
+ * Keeps only the most recent versions per item
68
+ */
69
+ cleanupOldVersions(maxVersionsPerItem?: number): Promise<void>;
70
+ /**
71
+ * Create default state
72
+ */
73
+ private createDefaultState;
74
+ /**
75
+ * Ensure state directory exists
76
+ */
77
+ private ensureStateDirectory;
78
+ /**
79
+ * Load state from disk
80
+ */
81
+ private loadFromDisk;
82
+ /**
83
+ * Save state to disk immediately
84
+ */
85
+ private saveToDisk;
86
+ /**
87
+ * Save state to disk with debouncing
88
+ * Prevents excessive writes during rapid updates
89
+ */
90
+ private saveToDiskDebounced;
91
+ /**
92
+ * Force save any pending changes
93
+ * Call this before deactivating the node
94
+ */
95
+ flush(): Promise<void>;
96
+ }