@oomol-lab/open-connector 1.7.0 → 1.8.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/README.md +4 -0
- package/assets/open-connector/catalog/apps/airtable.json +134 -0
- package/assets/open-connector/catalog/apps/aisa.json +916 -76
- package/assets/open-connector/catalog/apps/cal.json +24 -6
- package/assets/open-connector/catalog/apps/discordbot.json +2 -2
- package/assets/open-connector/catalog/apps/enormail.json +411 -0
- package/assets/open-connector/catalog/apps/feishu_app_bot.json +158 -0
- package/assets/open-connector/catalog/apps/github.json +275 -0
- package/assets/open-connector/catalog/apps/gitlab.json +125 -0
- package/assets/open-connector/catalog/apps/gmail.json +151 -0
- package/assets/open-connector/catalog/apps/googlecalendar.json +206 -0
- package/assets/open-connector/catalog/apps/googledrive.json +479 -0
- package/assets/open-connector/catalog/apps/googlesheets.json +144 -0
- package/assets/open-connector/catalog/apps/hithink_finance.json +6 -3
- package/assets/open-connector/catalog/apps/indexed.json +1115 -0
- package/assets/open-connector/catalog/apps/linear.json +116 -0
- package/assets/open-connector/catalog/apps/notion.json +142 -0
- package/assets/open-connector/catalog/apps/one_drive.json +185 -0
- package/assets/open-connector/catalog/apps/outlook.json +182 -0
- package/assets/open-connector/catalog/apps/shopify.json +282 -0
- package/assets/open-connector/catalog/apps/slack.json +182 -0
- package/assets/open-connector/catalog/apps/spotify.json +46 -43
- package/assets/open-connector/catalog/apps/stripe.json +96 -0
- package/assets/open-connector/catalog/apps/telegram.json +138 -0
- package/assets/open-connector/catalog/apps/weixin_store.json +543 -2
- package/assets/open-connector/catalog/apps/woocommerce.json +127 -0
- package/assets/open-connector/catalog/apps/zendesk.json +152 -0
- package/assets/open-connector/catalog/apps/zerotier.json +9 -9
- package/assets/open-connector/catalog/apps-index.json +1 -1
- package/assets/open-connector/migrations/0014_saas_project.sql +55 -0
- package/assets/open-connector/migrations/0015_saas_cleanup_runtime.sql +8 -0
- package/assets/open-connector/migrations/0016_trigger_policy.sql +1 -0
- package/assets/open-connector/migrations/0017_trigger_subscriptions.sql +17 -0
- package/assets/open-connector/migrations/postgresql/0014_saas_project.sql +55 -0
- package/assets/open-connector/migrations/postgresql/0015_saas_cleanup_runtime.sql +8 -0
- package/assets/open-connector/migrations/postgresql/0016_trigger_policy.sql +1 -0
- package/assets/open-connector/migrations/postgresql/0017_trigger_subscriptions.sql +17 -0
- package/package.json +2 -2
- package/src/connection-service.d.ts +40 -7
- package/src/connection-service.js +60 -18
- package/src/core/action-policy.d.ts +8 -1
- package/src/core/action-policy.js +51 -0
- package/src/core/cast.d.ts +3 -0
- package/src/core/cast.js +4 -1
- package/src/core/guarded-fetch.d.ts +5 -0
- package/src/core/guarded-fetch.js +13 -4
- package/src/core/remote-http.d.ts +33 -0
- package/src/core/remote-http.js +104 -0
- package/src/core/request.d.ts +1 -0
- package/src/core/request.js +7 -0
- package/src/core/types.d.ts +4 -0
- package/src/marketplace/marketplace-service.d.ts +0 -1
- package/src/marketplace/marketplace-service.js +80 -92
- package/src/mcp.js +19 -5
- package/src/oauth/oauth-flow-service.d.ts +8 -1
- package/src/oauth/oauth-flow-service.js +10 -4
- package/src/providers/action-contracts.generated.d.ts +4 -2
- package/src/providers/airtable/executors.d.ts +3 -0
- package/src/providers/airtable/executors.js +2 -0
- package/src/providers/airtable/trigger-on-record-changed.d.ts +2 -0
- package/src/providers/airtable/trigger-on-record-changed.definition.d.ts +4 -0
- package/src/providers/airtable/trigger-on-record-changed.definition.js +54 -0
- package/src/providers/airtable/trigger-on-record-changed.js +235 -0
- package/src/providers/aisa/executors.js +196 -22
- package/src/providers/cal/actions.js +5 -1
- package/src/providers/enormail/executors.d.ts +4 -0
- package/src/providers/enormail/executors.js +97 -0
- package/src/providers/feishu_app_bot/executors.d.ts +3 -0
- package/src/providers/feishu_app_bot/executors.js +2 -0
- package/src/providers/feishu_app_bot/trigger-config.d.ts +1 -0
- package/src/providers/feishu_app_bot/trigger-config.js +10 -0
- package/src/providers/feishu_app_bot/trigger-on-event.d.ts +2 -0
- package/src/providers/feishu_app_bot/trigger-on-event.definition.d.ts +4 -0
- package/src/providers/feishu_app_bot/trigger-on-event.definition.js +63 -0
- package/src/providers/feishu_app_bot/trigger-on-event.js +29 -0
- package/src/providers/feishu_app_bot/trigger-subscriptions.d.ts +5 -0
- package/src/providers/feishu_app_bot/trigger-subscriptions.js +70 -0
- package/src/providers/github/executors.d.ts +3 -0
- package/src/providers/github/executors.js +6 -0
- package/src/providers/github/trigger-on-repo-event.d.ts +2 -0
- package/src/providers/github/trigger-on-repo-event.definition.d.ts +4 -0
- package/src/providers/github/trigger-on-repo-event.definition.js +94 -0
- package/src/providers/github/trigger-on-repo-event.js +168 -0
- package/src/providers/github/trigger-watch-pull-request.d.ts +2 -0
- package/src/providers/github/trigger-watch-pull-request.definition.d.ts +4 -0
- package/src/providers/github/trigger-watch-pull-request.definition.js +49 -0
- package/src/providers/github/trigger-watch-pull-request.js +106 -0
- package/src/providers/gitlab/executors.d.ts +3 -0
- package/src/providers/gitlab/executors.js +2 -0
- package/src/providers/gitlab/trigger-on-project-event.d.ts +2 -0
- package/src/providers/gitlab/trigger-on-project-event.definition.d.ts +5 -0
- package/src/providers/gitlab/trigger-on-project-event.definition.js +63 -0
- package/src/providers/gitlab/trigger-on-project-event.js +186 -0
- package/src/providers/gmail/executors.d.ts +3 -0
- package/src/providers/gmail/executors.js +2 -0
- package/src/providers/gmail/trigger-on-message-received.d.ts +2 -0
- package/src/providers/gmail/trigger-on-message-received.definition.d.ts +4 -0
- package/src/providers/gmail/trigger-on-message-received.definition.js +74 -0
- package/src/providers/gmail/trigger-on-message-received.js +232 -0
- package/src/providers/googlecalendar/executors.d.ts +3 -0
- package/src/providers/googlecalendar/executors.js +2 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.d.ts +2 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.definition.d.ts +4 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.definition.js +74 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.js +233 -0
- package/src/providers/googledrive/executors.d.ts +3 -0
- package/src/providers/googledrive/executors.js +7 -0
- package/src/providers/googledrive/trigger-changes.d.ts +12 -0
- package/src/providers/googledrive/trigger-changes.definition.d.ts +7 -0
- package/src/providers/googledrive/trigger-changes.definition.js +101 -0
- package/src/providers/googledrive/trigger-changes.js +452 -0
- package/src/providers/googledrive/trigger-on-file-change.d.ts +2 -0
- package/src/providers/googledrive/trigger-on-file-change.definition.d.ts +4 -0
- package/src/providers/googledrive/trigger-on-file-change.definition.js +77 -0
- package/src/providers/googledrive/trigger-on-file-change.js +258 -0
- package/src/providers/googlesheets/executors.d.ts +3 -0
- package/src/providers/googlesheets/executors.js +2 -0
- package/src/providers/googlesheets/trigger-on-row-added.d.ts +2 -0
- package/src/providers/googlesheets/trigger-on-row-added.definition.d.ts +4 -0
- package/src/providers/googlesheets/trigger-on-row-added.definition.js +54 -0
- package/src/providers/googlesheets/trigger-on-row-added.js +234 -0
- package/src/providers/hithink_finance/runtime.js +1 -1
- package/src/providers/indexed/executors.d.ts +4 -0
- package/src/providers/indexed/executors.js +16 -0
- package/src/providers/indexed/runtime.d.ts +26 -0
- package/src/providers/indexed/runtime.js +129 -0
- package/src/providers/linear/executors.d.ts +3 -0
- package/src/providers/linear/executors.js +2 -0
- package/src/providers/linear/trigger-on-issue-changed.d.ts +2 -0
- package/src/providers/linear/trigger-on-issue-changed.definition.d.ts +5 -0
- package/src/providers/linear/trigger-on-issue-changed.definition.js +43 -0
- package/src/providers/linear/trigger-on-issue-changed.js +215 -0
- package/src/providers/notion/executors.d.ts +3 -0
- package/src/providers/notion/executors.js +2 -0
- package/src/providers/notion/trigger-on-database-page-event.d.ts +2 -0
- package/src/providers/notion/trigger-on-database-page-event.definition.d.ts +4 -0
- package/src/providers/notion/trigger-on-database-page-event.definition.js +63 -0
- package/src/providers/notion/trigger-on-database-page-event.js +293 -0
- package/src/providers/one_drive/executors.d.ts +3 -0
- package/src/providers/one_drive/executors.js +2 -0
- package/src/providers/one_drive/trigger-on-item-changed.d.ts +2 -0
- package/src/providers/one_drive/trigger-on-item-changed.definition.d.ts +4 -0
- package/src/providers/one_drive/trigger-on-item-changed.definition.js +63 -0
- package/src/providers/one_drive/trigger-on-item-changed.js +214 -0
- package/src/providers/outlook/executors.d.ts +3 -0
- package/src/providers/outlook/executors.js +2 -0
- package/src/providers/outlook/trigger-on-message-received.d.ts +2 -0
- package/src/providers/outlook/trigger-on-message-received.definition.d.ts +4 -0
- package/src/providers/outlook/trigger-on-message-received.definition.js +93 -0
- package/src/providers/outlook/trigger-on-message-received.js +226 -0
- package/src/providers/outlook_calendar/executors.js +6 -0
- package/src/providers/provider-loader.d.ts +5 -0
- package/src/providers/provider-loader.js +4 -0
- package/src/providers/registry.generated.js +2 -0
- package/src/providers/shopify/executors.d.ts +3 -0
- package/src/providers/shopify/executors.js +2 -0
- package/src/providers/shopify/trigger-on-shop-event.d.ts +2 -0
- package/src/providers/shopify/trigger-on-shop-event.definition.d.ts +4 -0
- package/src/providers/shopify/trigger-on-shop-event.definition.js +214 -0
- package/src/providers/shopify/trigger-on-shop-event.js +165 -0
- package/src/providers/slack/executors.d.ts +3 -0
- package/src/providers/slack/executors.js +2 -0
- package/src/providers/slack/trigger-on-message-posted.d.ts +2 -0
- package/src/providers/slack/trigger-on-message-posted.definition.d.ts +4 -0
- package/src/providers/slack/trigger-on-message-posted.definition.js +80 -0
- package/src/providers/slack/trigger-on-message-posted.js +176 -0
- package/src/providers/spotify/runtime.js +15 -4
- package/src/providers/stripe/executors.d.ts +3 -0
- package/src/providers/stripe/executors.js +2 -0
- package/src/providers/stripe/trigger-on-event.d.ts +2 -0
- package/src/providers/stripe/trigger-on-event.definition.d.ts +4 -0
- package/src/providers/stripe/trigger-on-event.definition.js +43 -0
- package/src/providers/stripe/trigger-on-event.js +212 -0
- package/src/providers/telegram/executors.d.ts +3 -0
- package/src/providers/telegram/executors.js +2 -0
- package/src/providers/telegram/trigger-on-update.d.ts +2 -0
- package/src/providers/telegram/trigger-on-update.definition.d.ts +5 -0
- package/src/providers/telegram/trigger-on-update.definition.js +78 -0
- package/src/providers/telegram/trigger-on-update.js +141 -0
- package/src/providers/weixin_store/access-token.d.ts +2 -1
- package/src/providers/weixin_store/access-token.js +15 -4
- package/src/providers/weixin_store/executors.js +97 -12
- package/src/providers/woocommerce/executors.d.ts +3 -0
- package/src/providers/woocommerce/executors.js +2 -0
- package/src/providers/woocommerce/trigger-on-store-event.d.ts +2 -0
- package/src/providers/woocommerce/trigger-on-store-event.definition.d.ts +4 -0
- package/src/providers/woocommerce/trigger-on-store-event.definition.js +55 -0
- package/src/providers/woocommerce/trigger-on-store-event.js +179 -0
- package/src/providers/zendesk/executors.d.ts +3 -0
- package/src/providers/zendesk/executors.js +2 -0
- package/src/providers/zendesk/trigger-on-event.d.ts +2 -0
- package/src/providers/zendesk/trigger-on-event.definition.d.ts +5 -0
- package/src/providers/zendesk/trigger-on-event.definition.js +101 -0
- package/src/providers/zendesk/trigger-on-event.js +188 -0
- package/src/saas/saas-cleanup-service.d.ts +26 -0
- package/src/saas/saas-cleanup-service.js +124 -0
- package/src/saas/saas-client.d.ts +86 -0
- package/src/saas/saas-client.js +369 -0
- package/src/saas/saas-execution-service.d.ts +15 -0
- package/src/saas/saas-execution-service.js +21 -0
- package/src/saas/saas-oauth-service.d.ts +25 -0
- package/src/saas/saas-oauth-service.js +197 -0
- package/src/saas/saas-project-service.d.ts +61 -0
- package/src/saas/saas-project-service.js +211 -0
- package/src/server/actions/action-idempotency.d.ts +1 -0
- package/src/server/actions/action-idempotency.js +1 -0
- package/src/server/actions/action-runner.d.ts +7 -0
- package/src/server/actions/action-runner.js +43 -7
- package/src/server/actions/run-log-summary.js +9 -0
- package/src/server/api/auth.d.ts +1 -0
- package/src/server/api/auth.js +13 -2
- package/src/server/api/cache-policy.js +4 -1
- package/src/server/api/connection-input.d.ts +7 -1
- package/src/server/api/connection-input.js +7 -1
- package/src/server/api/connection-routes.d.ts +3 -1
- package/src/server/api/connection-routes.js +21 -3
- package/src/server/api/http-utils.d.ts +4 -3
- package/src/server/api/oauth-completion-page.d.ts +124 -0
- package/src/server/api/oauth-completion-page.js +113 -18
- package/src/server/api/openapi.js +112 -14
- package/src/server/api/policy-input.js +6 -0
- package/src/server/api/runtime-api.d.ts +10 -2
- package/src/server/api/runtime-api.js +17 -4
- package/src/server/api/saas-completion-page.d.ts +2 -0
- package/src/server/api/saas-completion-page.js +103 -0
- package/src/server/connect-app.d.ts +5 -0
- package/src/server/connect-app.js +50 -0
- package/src/server/connect-server.d.ts +12 -0
- package/src/server/connect-server.js +147 -13
- package/src/server/connector-runtime.d.ts +3 -1
- package/src/server/connector-runtime.js +5 -2
- package/src/server/files/transit-files.js +16 -7
- package/src/server/proxy/proxy-runner.d.ts +5 -1
- package/src/server/proxy/proxy-runner.js +72 -26
- package/src/server/storage/connection-request-store.d.ts +41 -1
- package/src/server/storage/connection-request-store.js +245 -20
- package/src/server/storage/connection-store.d.ts +16 -0
- package/src/server/storage/connection-store.js +119 -0
- package/src/server/storage/node-runtime-database.js +3 -3
- package/src/server/storage/{postgres-migrations.d.ts → postgres/migrations.d.ts} +2 -2
- package/src/server/storage/{postgres-migrations.js → postgres/migrations.js} +1 -1
- package/src/server/storage/postgres/runtime-store.d.ts +43 -0
- package/src/server/storage/{postgres-runtime-store.js → postgres/runtime-store.js} +59 -94
- package/src/server/storage/runtime-database.d.ts +4 -0
- package/src/server/storage/runtime-sql.d.ts +1 -1
- package/src/server/storage/runtime-sql.js +2 -1
- package/src/server/storage/runtime-store.d.ts +1 -0
- package/src/server/storage/runtime-token-service.d.ts +2 -0
- package/src/server/storage/runtime-token-service.js +3 -0
- package/src/server/storage/saas-project-store.d.ts +77 -0
- package/src/server/storage/saas-project-store.js +273 -0
- package/src/server/storage/{sqlite-runtime-store.d.ts → sqlite/runtime-store.d.ts} +21 -25
- package/src/server/storage/{sqlite-runtime-store.js → sqlite/runtime-store.js} +100 -115
- package/src/server/storage/trigger-store.d.ts +16 -0
- package/src/server/storage/trigger-store.js +96 -0
- package/src/triggers/common/config.d.ts +2 -0
- package/src/triggers/common/config.js +17 -0
- package/src/triggers/common/configOptions.d.ts +13 -0
- package/src/triggers/common/configOptions.js +1 -0
- package/src/triggers/common/encoding.d.ts +3 -0
- package/src/triggers/common/encoding.js +19 -0
- package/src/triggers/common/integration.d.ts +115 -0
- package/src/triggers/common/integration.js +31 -0
- package/src/triggers/common/json.d.ts +6 -0
- package/src/triggers/common/json.js +91 -0
- package/src/triggers/common/poll.d.ts +41 -0
- package/src/triggers/common/poll.js +15 -0
- package/src/triggers/common/proxy.d.ts +14 -0
- package/src/triggers/common/proxy.js +1 -0
- package/src/triggers/common/types.d.ts +42 -0
- package/src/triggers/common/types.js +1 -0
- package/src/triggers/maintenance.d.ts +40 -0
- package/src/triggers/maintenance.js +120 -0
- package/src/triggers/metadata.d.ts +8 -0
- package/src/triggers/metadata.js +1 -0
- package/src/triggers/request.d.ts +33 -0
- package/src/triggers/request.js +52 -0
- package/src/triggers/resources.d.ts +12 -0
- package/src/triggers/resources.js +121 -0
- package/src/triggers/signature.d.ts +4 -0
- package/src/triggers/signature.js +48 -0
- package/src/triggers/store.d.ts +29 -0
- package/src/triggers/store.js +1 -0
- package/src/triggers/subscriptions.d.ts +22 -0
- package/src/triggers/subscriptions.js +220 -0
- package/src/triggers/trigger-runner.d.ts +34 -0
- package/src/triggers/trigger-runner.js +169 -0
- package/src/server/storage/postgres-runtime-store.d.ts +0 -39
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { eventsPollOutputs, maximumPollEventsPerPage, PermanentPollError, PollConnectionError, TransientPollError, } from "../../triggers/common/poll.js";
|
|
2
|
+
import { snapshot } from "./trigger-on-file-change.definition.js";
|
|
3
|
+
const folderMimeType = "application/vnd.google-apps.folder";
|
|
4
|
+
const listFields = "nextPageToken,incompleteSearch,files(id,name,mimeType,createdTime,modifiedTime,parents,trashed,webViewLink,driveId,size,version,lastModifyingUser(displayName,emailAddress))";
|
|
5
|
+
const transientReasons = new Set([
|
|
6
|
+
"rateLimitExceeded",
|
|
7
|
+
"userRateLimitExceeded",
|
|
8
|
+
"dailyLimitExceeded",
|
|
9
|
+
"sharingRateLimitExceeded",
|
|
10
|
+
]);
|
|
11
|
+
const permanentReasons = new Set(["insufficientFilePermissions", "appNotAuthorizedToFile"]);
|
|
12
|
+
export const googleDriveFileChange = {
|
|
13
|
+
buildOutputs: eventsPollOutputs,
|
|
14
|
+
snapshot,
|
|
15
|
+
async poll(context) {
|
|
16
|
+
const config = resolveConfig(context.config);
|
|
17
|
+
if (context.checkpoint === null)
|
|
18
|
+
return seed(context, config);
|
|
19
|
+
const checkpoint = readCheckpoint(context.checkpoint);
|
|
20
|
+
if (checkpoint.changeType !== config.changeType) {
|
|
21
|
+
return {
|
|
22
|
+
checkpoint: seeded(context.now, config.changeType),
|
|
23
|
+
events: [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return pollFolder(context, config, checkpoint);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
function resolveConfig(value) {
|
|
30
|
+
return {
|
|
31
|
+
changeType: value.changeType,
|
|
32
|
+
driveId: value.driveId,
|
|
33
|
+
folderId: value.folderId,
|
|
34
|
+
itemTypes: new Set(value.itemTypes),
|
|
35
|
+
maxFilesPerPoll: value.maxFilesPerPoll,
|
|
36
|
+
mimeTypes: value.mimeTypes,
|
|
37
|
+
namePrefix: value.namePrefix,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function readCheckpoint(value) {
|
|
41
|
+
const checkpoint = record(value);
|
|
42
|
+
if (typeof checkpoint?.since != "string" || Number.isNaN(Date.parse(checkpoint.since))) {
|
|
43
|
+
throw new PermanentPollError("Google Drive checkpoint is invalid; recreate the Trigger.");
|
|
44
|
+
}
|
|
45
|
+
const boundary = checkpoint.boundaryKeys;
|
|
46
|
+
return {
|
|
47
|
+
since: checkpoint.since,
|
|
48
|
+
...(typeof checkpoint.floor == "string" ? { floor: checkpoint.floor } : {}),
|
|
49
|
+
...(typeof checkpoint.pageToken == "string" ? { pageToken: checkpoint.pageToken } : {}),
|
|
50
|
+
...(typeof checkpoint.pageQuery == "string" ? { pageQuery: checkpoint.pageQuery } : {}),
|
|
51
|
+
...(typeof checkpoint.changeType == "string" ? { changeType: checkpoint.changeType } : {}),
|
|
52
|
+
...(typeof checkpoint.maxSeen == "string" ? { maxSeen: checkpoint.maxSeen } : {}),
|
|
53
|
+
...(Array.isArray(boundary)
|
|
54
|
+
? { boundaryKeys: boundary.filter((key) => typeof key == "string") }
|
|
55
|
+
: {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function seed(context, config) {
|
|
59
|
+
const result = await get(context, `/drive/v3/files/${encodeURIComponent(config.folderId)}`, {
|
|
60
|
+
fields: "id,name,mimeType,driveId,trashed",
|
|
61
|
+
supportsAllDrives: "true",
|
|
62
|
+
});
|
|
63
|
+
success(result, "folder probe");
|
|
64
|
+
const probe = record(result.data);
|
|
65
|
+
if (probe == null)
|
|
66
|
+
throw new TransientPollError("Google Drive folder probe returned no payload.");
|
|
67
|
+
if (probe.mimeType !== folderMimeType)
|
|
68
|
+
throw new PermanentPollError("Google Drive folderId does not point to a folder.");
|
|
69
|
+
if (probe.trashed === true)
|
|
70
|
+
throw new PermanentPollError("The Google Drive folder is in the trash.");
|
|
71
|
+
if (config.driveId.length > 0 && (probe.driveId ?? "") !== config.driveId) {
|
|
72
|
+
throw new PermanentPollError("The configured Google Drive shared drive does not contain this folder.");
|
|
73
|
+
}
|
|
74
|
+
return { checkpoint: seeded(context.now, config.changeType), events: [] };
|
|
75
|
+
}
|
|
76
|
+
function seeded(now, change) {
|
|
77
|
+
const since = now.toISOString();
|
|
78
|
+
return { changeType: change, floor: since, since };
|
|
79
|
+
}
|
|
80
|
+
async function pollFolder(context, config, checkpoint) {
|
|
81
|
+
const query = filesQuery(config, checkpoint.since, checkpoint.floor);
|
|
82
|
+
const fingerprint = `${config.driveId}|${query}`;
|
|
83
|
+
const pageToken = checkpoint.pageQuery == fingerprint ? checkpoint.pageToken : undefined;
|
|
84
|
+
const result = await get(context, "/drive/v3/files", {
|
|
85
|
+
corpora: config.driveId.length == 0 ? "allDrives" : "drive",
|
|
86
|
+
driveId: config.driveId.length == 0 ? undefined : config.driveId,
|
|
87
|
+
fields: listFields,
|
|
88
|
+
includeItemsFromAllDrives: "true",
|
|
89
|
+
pageSize: Math.min(config.maxFilesPerPoll, maximumPollEventsPerPage),
|
|
90
|
+
pageToken,
|
|
91
|
+
q: query,
|
|
92
|
+
spaces: "drive",
|
|
93
|
+
supportsAllDrives: "true",
|
|
94
|
+
});
|
|
95
|
+
success(result, "files list");
|
|
96
|
+
const page = (record(result.data) ?? {});
|
|
97
|
+
const delivered = new Set(checkpoint.boundaryKeys ?? []);
|
|
98
|
+
const events = [];
|
|
99
|
+
const consumed = [];
|
|
100
|
+
let maxSeen = pageToken == null ? undefined : checkpoint.maxSeen;
|
|
101
|
+
for (const file of page.files ?? []) {
|
|
102
|
+
if (file.id == null || file.id.length == 0)
|
|
103
|
+
continue;
|
|
104
|
+
const timestamp = trackedTime(file, config.changeType);
|
|
105
|
+
if (timestamp == null)
|
|
106
|
+
continue;
|
|
107
|
+
const value = event(file, file.id, timestamp, config.changeType);
|
|
108
|
+
consumed.push(value.dedupeKey);
|
|
109
|
+
if (later(timestamp, maxSeen))
|
|
110
|
+
maxSeen = timestamp;
|
|
111
|
+
if (!delivered.has(value.dedupeKey))
|
|
112
|
+
events.push(value);
|
|
113
|
+
}
|
|
114
|
+
if (page.incompleteSearch === true) {
|
|
115
|
+
const drain = pageToken == null ? {} : pageState(pageToken, fingerprint, maxSeen);
|
|
116
|
+
return {
|
|
117
|
+
checkpoint: checkpointValue(checkpoint, config.changeType, checkpoint.since, consumed, drain),
|
|
118
|
+
events,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (page.nextPageToken) {
|
|
122
|
+
return {
|
|
123
|
+
checkpoint: checkpointValue(checkpoint, config.changeType, checkpoint.since, consumed, pageState(page.nextPageToken, fingerprint, maxSeen)),
|
|
124
|
+
events,
|
|
125
|
+
hasMore: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const since = maxSeen ?? checkpoint.since;
|
|
129
|
+
return {
|
|
130
|
+
checkpoint: checkpointValue(checkpoint, config.changeType, since, consumed),
|
|
131
|
+
events,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function pageState(pageToken, pageQuery, maxSeen) {
|
|
135
|
+
return { pageQuery, pageToken, ...(maxSeen == null ? {} : { maxSeen }) };
|
|
136
|
+
}
|
|
137
|
+
function checkpointValue(previous, change, since, consumed, page = {}) {
|
|
138
|
+
const boundaryKeys = [...new Set([...(previous.boundaryKeys ?? []), ...consumed])]
|
|
139
|
+
.map((key) => ({ at: Date.parse(key.slice(key.indexOf(":") + 1)), key }))
|
|
140
|
+
.filter(({ at }) => at >= windowStart(since, previous.floor))
|
|
141
|
+
.toSorted((left, right) => right.at - left.at)
|
|
142
|
+
.slice(0, 200)
|
|
143
|
+
.map(({ key }) => key);
|
|
144
|
+
return {
|
|
145
|
+
changeType: change,
|
|
146
|
+
since,
|
|
147
|
+
...floorField(previous.floor, since),
|
|
148
|
+
...page,
|
|
149
|
+
...(boundaryKeys.length == 0 ? {} : { boundaryKeys }),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function filesQuery(config, since, floorValue) {
|
|
153
|
+
const field = config.changeType == "created" ? "createdTime" : "modifiedTime";
|
|
154
|
+
const terms = [
|
|
155
|
+
`'${escape(config.folderId)}' in parents`,
|
|
156
|
+
"trashed = false",
|
|
157
|
+
`${field} >= '${new Date(windowStart(since, floorValue)).toISOString()}'`,
|
|
158
|
+
];
|
|
159
|
+
if (!config.itemTypes.has("folder"))
|
|
160
|
+
terms.push(`mimeType != '${folderMimeType}'`);
|
|
161
|
+
else if (!config.itemTypes.has("file"))
|
|
162
|
+
terms.push(`mimeType = '${folderMimeType}'`);
|
|
163
|
+
if (config.mimeTypes.length > 0)
|
|
164
|
+
terms.push(`(${config.mimeTypes.map((type) => `mimeType = '${escape(type)}'`).join(" or ")})`);
|
|
165
|
+
if (config.namePrefix.length > 0)
|
|
166
|
+
terms.push(`name contains '${escape(config.namePrefix)}'`);
|
|
167
|
+
return terms.join(" and ");
|
|
168
|
+
}
|
|
169
|
+
function windowStart(since, floorValue) {
|
|
170
|
+
const start = Date.parse(since) - 60_000;
|
|
171
|
+
const minimum = floorValue == null ? Number.NaN : Date.parse(floorValue);
|
|
172
|
+
return Number.isNaN(minimum) ? start : Math.max(start, minimum);
|
|
173
|
+
}
|
|
174
|
+
function floorField(value, since) {
|
|
175
|
+
if (value == null || Number.isNaN(Date.parse(value)) || Date.parse(since) - 60_000 >= Date.parse(value))
|
|
176
|
+
return {};
|
|
177
|
+
return { floor: value };
|
|
178
|
+
}
|
|
179
|
+
function trackedTime(file, change) {
|
|
180
|
+
const value = change == "created" ? file.createdTime : file.modifiedTime;
|
|
181
|
+
return value == null || value.length == 0 || Number.isNaN(Date.parse(value)) ? undefined : value;
|
|
182
|
+
}
|
|
183
|
+
function event(file, id, timestamp, change) {
|
|
184
|
+
const itemType = file.mimeType == folderMimeType ? "folder" : "file";
|
|
185
|
+
const user = file.lastModifyingUser;
|
|
186
|
+
return {
|
|
187
|
+
dedupeKey: `${id}:${timestamp}`,
|
|
188
|
+
payload: {
|
|
189
|
+
createdTime: file.createdTime ?? null,
|
|
190
|
+
driveId: file.driveId ?? null,
|
|
191
|
+
eventType: `${itemType}_${change}`,
|
|
192
|
+
fileId: id,
|
|
193
|
+
itemType,
|
|
194
|
+
lastModifyingUser: user == null ? null : { displayName: user.displayName ?? "", emailAddress: user.emailAddress ?? "" },
|
|
195
|
+
mimeType: file.mimeType ?? "",
|
|
196
|
+
modifiedTime: file.modifiedTime ?? null,
|
|
197
|
+
name: file.name ?? "",
|
|
198
|
+
parents: file.parents ?? [],
|
|
199
|
+
...(file.size == null ? {} : { size: file.size }),
|
|
200
|
+
webViewLink: file.webViewLink ?? null,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function get(context, endpoint, query) {
|
|
205
|
+
try {
|
|
206
|
+
return await context.connector.execute({
|
|
207
|
+
endpoint,
|
|
208
|
+
method: "GET",
|
|
209
|
+
query: Object.fromEntries(Object.entries(query).filter((entry) => entry[1] != null)),
|
|
210
|
+
}, context.signal);
|
|
211
|
+
}
|
|
212
|
+
catch (cause) {
|
|
213
|
+
throw new TransientPollError(`Google Drive proxy request to ${endpoint} failed.`, { cause });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function success(result, operation) {
|
|
217
|
+
if (result.status >= 200 && result.status < 300)
|
|
218
|
+
return;
|
|
219
|
+
if (result.status == 400 || result.status == 404)
|
|
220
|
+
throw new PermanentPollError(`Google Drive ${operation} rejected the configuration.`);
|
|
221
|
+
if (result.status == 401)
|
|
222
|
+
throw new PollConnectionError(`Google Drive ${operation} rejected the Connection.`);
|
|
223
|
+
if (result.status == 403) {
|
|
224
|
+
const values = reasons(result.data);
|
|
225
|
+
if (values.some((reason) => transientReasons.has(reason)))
|
|
226
|
+
throw new TransientPollError(`Google Drive ${operation} was rate limited.`);
|
|
227
|
+
if (values.some((reason) => permanentReasons.has(reason)))
|
|
228
|
+
throw new PermanentPollError(`Google Drive ${operation} was rejected by an ACL.`);
|
|
229
|
+
if (insufficientScopes(result.data))
|
|
230
|
+
throw new PollConnectionError(`Google Drive ${operation} needs additional scopes.`);
|
|
231
|
+
}
|
|
232
|
+
throw new TransientPollError(`Google Drive ${operation} failed with status ${result.status}.`);
|
|
233
|
+
}
|
|
234
|
+
function reasons(data) {
|
|
235
|
+
const error = record(record(data)?.error);
|
|
236
|
+
const nested = [error?.errors, error?.details].flatMap((value) => (Array.isArray(value) ? value : []));
|
|
237
|
+
return [...nested.map((value) => record(value)?.reason), error?.status].filter((value) => typeof value == "string");
|
|
238
|
+
}
|
|
239
|
+
function insufficientScopes(data) {
|
|
240
|
+
if (reasons(data).includes("ACCESS_TOKEN_SCOPE_INSUFFICIENT"))
|
|
241
|
+
return true;
|
|
242
|
+
const message = record(record(data)?.error)?.message;
|
|
243
|
+
return typeof message == "string" && message.toLowerCase().includes("insufficient authentication scopes");
|
|
244
|
+
}
|
|
245
|
+
function escape(value) {
|
|
246
|
+
return value.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
|
|
247
|
+
}
|
|
248
|
+
function later(candidate, current) {
|
|
249
|
+
const candidateMs = Date.parse(candidate);
|
|
250
|
+
if (Number.isNaN(candidateMs))
|
|
251
|
+
return false;
|
|
252
|
+
return current == null || Number.isNaN(Date.parse(current)) || candidateMs > Date.parse(current);
|
|
253
|
+
}
|
|
254
|
+
function record(value) {
|
|
255
|
+
return value != null && typeof value == "object" && !Array.isArray(value)
|
|
256
|
+
? value
|
|
257
|
+
: undefined;
|
|
258
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { CredentialValidators, ProviderExecutors, ProviderProxyExecutor } from "../../core/types.ts";
|
|
2
|
+
import type { IntegrationDefinition } from "../../triggers/common/integration.ts";
|
|
3
|
+
import type { PollDefinition } from "../../triggers/common/poll.ts";
|
|
2
4
|
import type { ProviderActionHandlers } from "../provider-runtime.ts";
|
|
3
5
|
import type { OAuthProviderContext } from "../provider-runtime.ts";
|
|
4
6
|
type ActionContext = OAuthProviderContext;
|
|
@@ -7,4 +9,5 @@ export declare const googlesheetsActionHandlers: ProviderActionHandlers<"googles
|
|
|
7
9
|
export declare const executors: ProviderExecutors;
|
|
8
10
|
export declare const credentialValidators: CredentialValidators;
|
|
9
11
|
export declare const proxy: ProviderProxyExecutor;
|
|
12
|
+
export declare const triggers: readonly (IntegrationDefinition | PollDefinition)[];
|
|
10
13
|
export {};
|
|
@@ -7,6 +7,7 @@ import { aggregateColumnData, createSpreadsheetColumn, createSpreadsheetRow, loo
|
|
|
7
7
|
import { batchGetValues, batchGetValuesByDataFilter, getValues } from "./runtime-values.js";
|
|
8
8
|
import { appendValues, batchClearValues, batchClearValuesByDataFilter, batchUpdateValuesByDataFilter, clearValues, updateValues, updateValuesBatch, } from "./runtime-values.js";
|
|
9
9
|
import { googlesheetsOAuthScopes } from "./scopes.js";
|
|
10
|
+
import { googleSheetsRowAdded } from "./trigger-on-row-added.js";
|
|
10
11
|
const service = "googlesheets";
|
|
11
12
|
const implementedActionHandlers = {
|
|
12
13
|
search_spreadsheets(input, { accessToken, fetcher }) {
|
|
@@ -158,3 +159,4 @@ export const proxy = defineProviderProxy({
|
|
|
158
159
|
baseUrl: "https://sheets.googleapis.com/v4",
|
|
159
160
|
auth: googleBearerProxyAuth(googlesheetsOAuthScopes),
|
|
160
161
|
});
|
|
162
|
+
export const triggers = [googleSheetsRowAdded];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsonSchema as s } from "../../core/json-schema.js";
|
|
2
|
+
const eventSchema = s.object({
|
|
3
|
+
row: { type: "object" },
|
|
4
|
+
rowNumber: s.integer(),
|
|
5
|
+
sheetId: s.integer(),
|
|
6
|
+
sheetTitle: s.string(),
|
|
7
|
+
spreadsheetId: s.string(),
|
|
8
|
+
values: s.array({}),
|
|
9
|
+
}, { additionalProperties: false, required: ["spreadsheetId", "sheetId", "sheetTitle", "rowNumber", "row", "values"] });
|
|
10
|
+
export const snapshot = {
|
|
11
|
+
configInputs: [
|
|
12
|
+
{
|
|
13
|
+
handle: "columnRange",
|
|
14
|
+
jsonSchema: s.string({ pattern: "^[A-Za-z]{1,3}(?::[A-Za-z]{1,3})?$" }),
|
|
15
|
+
nullable: false,
|
|
16
|
+
value: "A:ZZZ",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
handle: "dateTimeRender",
|
|
20
|
+
jsonSchema: s.stringEnum(["FORMATTED_STRING", "SERIAL_NUMBER"]),
|
|
21
|
+
nullable: false,
|
|
22
|
+
value: "FORMATTED_STRING",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
handle: "firstDataRow",
|
|
26
|
+
jsonSchema: s.integer({ minimum: 1 }),
|
|
27
|
+
nullable: false,
|
|
28
|
+
value: 2,
|
|
29
|
+
},
|
|
30
|
+
{ handle: "headerRow", jsonSchema: s.integer({ minimum: 1 }), nullable: false, value: 1 },
|
|
31
|
+
{
|
|
32
|
+
handle: "maxRowsPerPoll",
|
|
33
|
+
jsonSchema: s.integer({ maximum: 500, minimum: 1 }),
|
|
34
|
+
nullable: false,
|
|
35
|
+
value: 100,
|
|
36
|
+
},
|
|
37
|
+
{ handle: "sheet", jsonSchema: s.string({ minLength: 1 }), nullable: false },
|
|
38
|
+
{ handle: "spreadsheetId", jsonSchema: s.string({ minLength: 1 }), nullable: false },
|
|
39
|
+
{
|
|
40
|
+
handle: "valueRender",
|
|
41
|
+
jsonSchema: s.stringEnum(["UNFORMATTED_VALUE", "FORMATTED_VALUE", "FORMULA"]),
|
|
42
|
+
nullable: false,
|
|
43
|
+
value: "UNFORMATTED_VALUE",
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
definitionVersion: 2,
|
|
47
|
+
description: "Polls a sheet and triggers once for every new row appended below the last row already seen.",
|
|
48
|
+
displayName: "New Row Added",
|
|
49
|
+
key: "googlesheets.on_row_added",
|
|
50
|
+
name: "on_row_added",
|
|
51
|
+
outputs: [{ handle: "events", jsonSchema: s.array(eventSchema), nullable: false }],
|
|
52
|
+
provider: "googlesheets",
|
|
53
|
+
type: "poll",
|
|
54
|
+
};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { eventsPollOutputs, maximumPollEventsPerPage, PermanentPollError, PollConnectionError, TransientPollError, } from "../../triggers/common/poll.js";
|
|
2
|
+
import { snapshot } from "./trigger-on-row-added.definition.js";
|
|
3
|
+
const permissionMarkers = [
|
|
4
|
+
"permission_denied",
|
|
5
|
+
"accessnotconfigured",
|
|
6
|
+
"insufficientfilepermissions",
|
|
7
|
+
"appnotauthorizedtofile",
|
|
8
|
+
"the caller does not have permission",
|
|
9
|
+
"has not been used in project",
|
|
10
|
+
];
|
|
11
|
+
const quotaMarkers = ["ratelimitexceeded", "dailylimitexceeded"];
|
|
12
|
+
export const googleSheetsRowAdded = {
|
|
13
|
+
buildOutputs: eventsPollOutputs,
|
|
14
|
+
snapshot,
|
|
15
|
+
async poll(context) {
|
|
16
|
+
const config = resolveConfig(context.config);
|
|
17
|
+
if (context.checkpoint === null) {
|
|
18
|
+
const sheet = await resolveSheet(context, config);
|
|
19
|
+
return { checkpoint: await anchor(context, config, sheet), events: [] };
|
|
20
|
+
}
|
|
21
|
+
const checkpoint = readCheckpoint(context.checkpoint);
|
|
22
|
+
const sheet = await resolveSheet(context, config);
|
|
23
|
+
if (checkpoint.spreadsheetId !== config.spreadsheetId || checkpoint.sheetId !== sheet.sheetId) {
|
|
24
|
+
return { checkpoint: await anchor(context, config, sheet), events: [] };
|
|
25
|
+
}
|
|
26
|
+
if (checkpoint.lastRowNumber > sheet.rowCount)
|
|
27
|
+
return { checkpoint: await anchor(context, config, sheet), events: [] };
|
|
28
|
+
const headers = await header(context, config, sheet);
|
|
29
|
+
const start = Math.min(Math.max(config.firstDataRow, checkpoint.lastRowNumber + 1), sheet.rowCount);
|
|
30
|
+
const end = Math.min(start + Math.min(config.maxRowsPerPoll, maximumPollEventsPerPage) - 1, sheet.rowCount);
|
|
31
|
+
const rows = await values(context, config, sheet, start, end, "row window fetch");
|
|
32
|
+
let last = start + rows.length - 1;
|
|
33
|
+
if (rows.length < end - start + 1) {
|
|
34
|
+
// API 会省略窗口尾部的空行,确认实际表尾后才能推进游标。
|
|
35
|
+
const tail = await lastNonEmptyRow(context, config, sheet);
|
|
36
|
+
if (tail < checkpoint.lastRowNumber)
|
|
37
|
+
return { checkpoint: { ...checkpoint, lastRowNumber: tail }, events: [] };
|
|
38
|
+
last = Math.min(end, tail);
|
|
39
|
+
}
|
|
40
|
+
if (last <= checkpoint.lastRowNumber)
|
|
41
|
+
return { checkpoint, events: [] };
|
|
42
|
+
const events = [];
|
|
43
|
+
let filtered = 0;
|
|
44
|
+
for (const [index, cells] of rows.entries()) {
|
|
45
|
+
const rowNumber = start + index;
|
|
46
|
+
if (rowNumber <= checkpoint.lastRowNumber)
|
|
47
|
+
continue;
|
|
48
|
+
if (rowNumber == config.headerRow || empty(cells)) {
|
|
49
|
+
filtered += 1;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
events.push(await event(config, sheet, rowNumber, cells, headers));
|
|
53
|
+
}
|
|
54
|
+
const hasMore = last == end && end < sheet.rowCount;
|
|
55
|
+
return {
|
|
56
|
+
checkpoint: {
|
|
57
|
+
lastRowNumber: last,
|
|
58
|
+
sheetId: sheet.sheetId,
|
|
59
|
+
spreadsheetId: config.spreadsheetId,
|
|
60
|
+
},
|
|
61
|
+
events,
|
|
62
|
+
filtered,
|
|
63
|
+
...(hasMore ? { hasMore: true } : {}),
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
function resolveConfig(value) {
|
|
68
|
+
const range = value.columnRange.trim();
|
|
69
|
+
const separator = range.indexOf(":");
|
|
70
|
+
const colStart = separator < 0 ? range : range.slice(0, separator);
|
|
71
|
+
const colEnd = separator < 0 ? range : range.slice(separator + 1);
|
|
72
|
+
const spreadsheet = value.spreadsheetId.trim();
|
|
73
|
+
return {
|
|
74
|
+
colEnd,
|
|
75
|
+
colStart,
|
|
76
|
+
dateTimeRender: value.dateTimeRender,
|
|
77
|
+
firstDataRow: value.firstDataRow,
|
|
78
|
+
headerRow: value.headerRow,
|
|
79
|
+
maxRowsPerPoll: value.maxRowsPerPoll,
|
|
80
|
+
sheet: value.sheet,
|
|
81
|
+
spreadsheetId: /\/spreadsheets\/d\/([A-Za-z0-9_-]+)/.exec(spreadsheet)?.[1] ?? spreadsheet,
|
|
82
|
+
valueRender: value.valueRender,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function readCheckpoint(value) {
|
|
86
|
+
const checkpoint = record(value);
|
|
87
|
+
if (typeof checkpoint?.spreadsheetId != "string" ||
|
|
88
|
+
!Number.isInteger(checkpoint.sheetId) ||
|
|
89
|
+
!Number.isInteger(checkpoint.lastRowNumber)) {
|
|
90
|
+
throw new PermanentPollError("Google Sheets checkpoint is invalid; recreate the Trigger.");
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
lastRowNumber: checkpoint.lastRowNumber,
|
|
94
|
+
sheetId: checkpoint.sheetId,
|
|
95
|
+
spreadsheetId: checkpoint.spreadsheetId,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function resolveSheet(context, config) {
|
|
99
|
+
const result = await get(context, `/spreadsheets/${encodeURIComponent(config.spreadsheetId)}`, {
|
|
100
|
+
fields: "sheets.properties(sheetId,title,sheetType,gridProperties)",
|
|
101
|
+
});
|
|
102
|
+
if (result.status == 404)
|
|
103
|
+
throw new PermanentPollError("The Google Sheets spreadsheet was not found.");
|
|
104
|
+
success(result, "spreadsheet metadata fetch");
|
|
105
|
+
const raw = record(result.data)?.sheets;
|
|
106
|
+
const properties = Array.isArray(raw)
|
|
107
|
+
? raw.map((value) => record(record(value)?.properties))
|
|
108
|
+
: [];
|
|
109
|
+
const candidates = properties.filter((value) => value != null);
|
|
110
|
+
const byId = /^\d+$/.test(config.sheet)
|
|
111
|
+
? candidates.find(({ sheetId }) => sheetId === Number(config.sheet))
|
|
112
|
+
: undefined;
|
|
113
|
+
const match = byId ?? candidates.find(({ title }) => title === config.sheet);
|
|
114
|
+
if (match == null)
|
|
115
|
+
throw new PermanentPollError(`Google Sheets tab "${config.sheet}" does not exist.`);
|
|
116
|
+
if (match.sheetType != null && match.sheetType !== "GRID")
|
|
117
|
+
throw new PermanentPollError("The Google Sheets tab is not a grid sheet.");
|
|
118
|
+
const rowCount = match.gridProperties?.rowCount;
|
|
119
|
+
if (match.sheetId == null || match.title == null || rowCount == null) {
|
|
120
|
+
throw new TransientPollError("Google Sheets tab metadata is incomplete.");
|
|
121
|
+
}
|
|
122
|
+
return { rowCount, sheetId: match.sheetId, title: match.title };
|
|
123
|
+
}
|
|
124
|
+
async function anchor(context, config, sheet) {
|
|
125
|
+
return {
|
|
126
|
+
lastRowNumber: await lastNonEmptyRow(context, config, sheet),
|
|
127
|
+
sheetId: sheet.sheetId,
|
|
128
|
+
spreadsheetId: config.spreadsheetId,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function lastNonEmptyRow(context, config, sheet) {
|
|
132
|
+
if (sheet.rowCount < config.firstDataRow)
|
|
133
|
+
return config.firstDataRow - 1;
|
|
134
|
+
const rows = await values(context, config, sheet, config.firstDataRow, sheet.rowCount, "full-range scan");
|
|
135
|
+
return rows.length == 0 ? config.firstDataRow - 1 : config.firstDataRow + rows.length - 1;
|
|
136
|
+
}
|
|
137
|
+
async function header(context, config, sheet) {
|
|
138
|
+
const result = await get(context, valuesEndpoint(config, sheet, config.headerRow, config.headerRow), {
|
|
139
|
+
majorDimension: "ROWS",
|
|
140
|
+
valueRenderOption: "FORMATTED_VALUE",
|
|
141
|
+
});
|
|
142
|
+
success(result, "header row fetch");
|
|
143
|
+
return readValues(result.data)[0] ?? [];
|
|
144
|
+
}
|
|
145
|
+
async function values(context, config, sheet, start, end, operation) {
|
|
146
|
+
const result = await get(context, valuesEndpoint(config, sheet, start, end), {
|
|
147
|
+
dateTimeRenderOption: config.dateTimeRender,
|
|
148
|
+
majorDimension: "ROWS",
|
|
149
|
+
valueRenderOption: config.valueRender,
|
|
150
|
+
});
|
|
151
|
+
success(result, operation);
|
|
152
|
+
return readValues(result.data);
|
|
153
|
+
}
|
|
154
|
+
function readValues(data) {
|
|
155
|
+
const rows = record(data)?.values;
|
|
156
|
+
return Array.isArray(rows) ? rows : [];
|
|
157
|
+
}
|
|
158
|
+
function empty(cells) {
|
|
159
|
+
return cells.every((cell) => cell === "" || cell == null);
|
|
160
|
+
}
|
|
161
|
+
async function event(config, sheet, rowNumber, cells, headers) {
|
|
162
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(cells)));
|
|
163
|
+
const hash = [...new Uint8Array(digest)]
|
|
164
|
+
.slice(0, 8)
|
|
165
|
+
.map((value) => value.toString(16).padStart(2, "0"))
|
|
166
|
+
.join("");
|
|
167
|
+
const row = {};
|
|
168
|
+
const start = columnNumber(config.colStart);
|
|
169
|
+
for (const [index, cell] of cells.entries()) {
|
|
170
|
+
const heading = headers[index];
|
|
171
|
+
row[typeof heading == "string" && heading.length > 0 ? heading : columnLetters(start + index)] = cell;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
dedupeKey: `${sheet.sheetId}:${rowNumber}:${hash}`,
|
|
175
|
+
payload: {
|
|
176
|
+
row,
|
|
177
|
+
rowNumber,
|
|
178
|
+
sheetId: sheet.sheetId,
|
|
179
|
+
sheetTitle: sheet.title,
|
|
180
|
+
spreadsheetId: config.spreadsheetId,
|
|
181
|
+
values: cells,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function valuesEndpoint(config, sheet, start, end) {
|
|
186
|
+
const title = encodeURIComponent(`'${sheet.title.replaceAll("'", "''")}'`);
|
|
187
|
+
return `/spreadsheets/${encodeURIComponent(config.spreadsheetId)}/values/${title}!${config.colStart}${start}:${config.colEnd}${end}`;
|
|
188
|
+
}
|
|
189
|
+
async function get(context, endpoint, query) {
|
|
190
|
+
try {
|
|
191
|
+
return await context.connector.execute({ endpoint, method: "GET", query }, context.signal);
|
|
192
|
+
}
|
|
193
|
+
catch (cause) {
|
|
194
|
+
throw new TransientPollError(`Google Sheets proxy request to ${endpoint} failed.`, { cause });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function success(result, operation) {
|
|
198
|
+
if (result.status >= 200 && result.status < 300)
|
|
199
|
+
return;
|
|
200
|
+
if (result.status == 401)
|
|
201
|
+
throw new PollConnectionError(`Google Sheets ${operation} rejected the Connection.`);
|
|
202
|
+
const haystack = JSON.stringify(result.data ?? null).toLowerCase();
|
|
203
|
+
if (result.status == 403) {
|
|
204
|
+
if (quotaMarkers.some((marker) => haystack.includes(marker)))
|
|
205
|
+
throw new TransientPollError(`Google Sheets ${operation} was rate limited.`);
|
|
206
|
+
if (permissionMarkers.some((marker) => haystack.includes(marker))) {
|
|
207
|
+
throw new PollConnectionError(`Google Sheets ${operation} rejected the Connection.`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (result.status == 400 && haystack.includes("unable to parse range")) {
|
|
211
|
+
throw new PermanentPollError("Google Sheets rejected the configured range.");
|
|
212
|
+
}
|
|
213
|
+
if (result.status == 413)
|
|
214
|
+
throw new PermanentPollError("Google Sheets response is too large; narrow columnRange.");
|
|
215
|
+
throw new TransientPollError(`Google Sheets ${operation} failed with status ${result.status}.`);
|
|
216
|
+
}
|
|
217
|
+
function columnNumber(letters) {
|
|
218
|
+
let value = 0;
|
|
219
|
+
for (const letter of letters.toUpperCase())
|
|
220
|
+
value = value * 26 + letter.charCodeAt(0) - 64;
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
function columnLetters(value) {
|
|
224
|
+
let letters = "";
|
|
225
|
+
for (let rest = value; rest > 0; rest = Math.floor((rest - 1) / 26)) {
|
|
226
|
+
letters = String.fromCharCode(65 + ((rest - 1) % 26)) + letters;
|
|
227
|
+
}
|
|
228
|
+
return letters;
|
|
229
|
+
}
|
|
230
|
+
function record(value) {
|
|
231
|
+
return value != null && typeof value == "object" && !Array.isArray(value)
|
|
232
|
+
? value
|
|
233
|
+
: undefined;
|
|
234
|
+
}
|
|
@@ -393,7 +393,7 @@ function getPool(get, path, input, context) {
|
|
|
393
393
|
function getFinancialStatements(path, input, context) {
|
|
394
394
|
return get(path, {
|
|
395
395
|
thscode: requiredInputString(input.thscode, "thscode"),
|
|
396
|
-
period: optionalString(input.period),
|
|
396
|
+
period: optionalString(input.period) ?? "annual",
|
|
397
397
|
limit: optionalNumber(input.limit),
|
|
398
398
|
start: optionalNumber(input.startTimeMs),
|
|
399
399
|
end: optionalNumber(input.endTimeMs),
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CredentialValidators, ProviderExecutors, ProviderProxyExecutor } from "../../core/types.ts";
|
|
2
|
+
export declare const executors: ProviderExecutors;
|
|
3
|
+
export declare const proxy: ProviderProxyExecutor;
|
|
4
|
+
export declare const credentialValidators: CredentialValidators;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineApiKeyProviderExecutors, defineProviderProxy } from "../provider-runtime.js";
|
|
2
|
+
import { indexedActionHandlers, indexedApiBaseUrl, readIndexedError, validateIndexedCredential } from "./runtime.js";
|
|
3
|
+
const service = "indexed";
|
|
4
|
+
export const executors = defineApiKeyProviderExecutors(service, indexedActionHandlers, {
|
|
5
|
+
skipDnsValidation: true,
|
|
6
|
+
});
|
|
7
|
+
export const proxy = defineProviderProxy({
|
|
8
|
+
service,
|
|
9
|
+
baseUrl: indexedApiBaseUrl,
|
|
10
|
+
auth: { type: "api_key_header", name: "X-API-Key" },
|
|
11
|
+
readError: readIndexedError,
|
|
12
|
+
skipDnsValidation: true,
|
|
13
|
+
});
|
|
14
|
+
export const credentialValidators = {
|
|
15
|
+
apiKey: validateIndexedCredential,
|
|
16
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CredentialValidationResult } from "../../core/types.ts";
|
|
2
|
+
import type { ApiKeyProviderContext, ProviderActionHandlers } from "../provider-runtime.ts";
|
|
3
|
+
import { ProviderRequestError } from "../provider-runtime.ts";
|
|
4
|
+
export declare const indexedApiBaseUrl = "https://indexed.vc/api/v1";
|
|
5
|
+
type IndexedPhase = "validate" | "execute";
|
|
6
|
+
type IndexedActionHandler = (input: Record<string, unknown>, context: ApiKeyProviderContext) => Promise<unknown>;
|
|
7
|
+
export declare const indexedActionHandlers: ProviderActionHandlers<"indexed", IndexedActionHandler>;
|
|
8
|
+
/**
|
|
9
|
+
* Validate a key with GET /usage, which the API documents as free of credit charges.
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateIndexedCredential(input: {
|
|
12
|
+
apiKey: string;
|
|
13
|
+
}, options: {
|
|
14
|
+
fetcher: typeof fetch;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}): Promise<CredentialValidationResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Read an Indexed error response and map it onto the runtime's status conventions.
|
|
19
|
+
* 402 is a billing state (credits exhausted), reported as insufficient_credit.
|
|
20
|
+
* 403 TIER_UPGRADE_REQUIRED is a plan limit rather than a credential failure, so
|
|
21
|
+
* it is reported as invalid input instead of prompting a reconnect. During key
|
|
22
|
+
* validation a 401 or 403 is a field error on the submitted key. Every other
|
|
23
|
+
* status keeps its upstream value, so a 404 stays a not-found result.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readIndexedError(response: Response, phase?: IndexedPhase): Promise<ProviderRequestError>;
|
|
26
|
+
export {};
|