@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.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/CHANGELOG.md +66 -67
- package/CLAUDE.md +148 -124
- package/README.md +165 -130
- package/admin-scripts/index.js +52 -0
- package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
- package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
- package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
- package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
- package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
- package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
- package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
- package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
- package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
- package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
- package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
- package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
- package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
- package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
- package/application/commands/admin-script-commands.js +348 -0
- package/application/commands/integration-commands.js +12 -6
- package/application/index.js +5 -3
- package/assertions/get.js +17 -7
- package/associations/association.js +59 -57
- package/core/CLAUDE.md +222 -156
- package/core/Worker.js +33 -12
- package/credential/credential-router.js +262 -0
- package/credential/repositories/credential-repository-documentdb.js +71 -1
- package/credential/repositories/credential-repository-factory.js +4 -6
- package/credential/repositories/credential-repository-interface.js +8 -2
- package/credential/repositories/credential-repository-mongo.js +45 -4
- package/credential/repositories/credential-repository-postgres.js +44 -3
- package/credential/repositories/credential-repository.js +3 -1
- package/credential/use-cases/delete-credential-for-user.js +50 -0
- package/credential/use-cases/get-credential-for-user.js +21 -3
- package/credential/use-cases/list-credentials-for-user.js +31 -0
- package/credential/use-cases/reauthorize-credential.js +103 -0
- package/credential/use-cases/update-authentication-status.js +5 -2
- package/database/MONGODB_TRANSACTION_FIX.md +72 -52
- package/database/config.js +13 -59
- package/database/documentdb-encryption-service.js +29 -13
- package/database/documentdb-utils.js +21 -12
- package/database/encryption/README.md +53 -36
- package/database/encryption/encryption-schema-registry.js +29 -23
- package/database/encryption/field-encryption-service.js +15 -4
- package/database/encryption/logger.js +3 -2
- package/database/encryption/prisma-encryption-extension.js +5 -8
- package/database/prisma.js +22 -14
- package/database/repositories/health-check-repository-factory.js +7 -5
- package/database/repositories/health-check-repository-interface.js +15 -5
- package/database/repositories/health-check-repository-postgres.js +1 -1
- package/database/use-cases/check-database-health-use-case.js +3 -2
- package/database/use-cases/check-database-state-use-case.js +8 -7
- package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
- package/database/use-cases/get-migration-status-use-case.js +4 -2
- package/database/use-cases/run-database-migration-use-case.js +41 -9
- package/database/use-cases/test-encryption-use-case.js +4 -5
- package/database/use-cases/trigger-database-migration-use-case.js +9 -8
- package/database/utils/mongodb-collection-utils.js +11 -3
- package/database/utils/mongodb-schema-init.js +10 -4
- package/database/utils/prisma-runner.js +144 -74
- package/database/utils/prisma-schema-parser.js +2 -2
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
- package/encrypt/Cryptor.js +54 -49
- package/encrypt/aes-encryption-key-provider.js +82 -0
- package/encrypt/encryption-key-provider-interface.js +50 -0
- package/errors/fetch-error.js +1 -1
- package/generated/prisma-mongodb/edge.js +59 -4
- package/generated/prisma-mongodb/index-browser.js +49 -1
- package/generated/prisma-mongodb/index.d.ts +4023 -61
- package/generated/prisma-mongodb/index.js +59 -4
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +87 -0
- package/generated/prisma-mongodb/wasm.js +59 -4
- package/generated/prisma-postgresql/edge.js +58 -4
- package/generated/prisma-postgresql/index-browser.js +48 -1
- package/generated/prisma-postgresql/index.d.ts +4738 -54
- package/generated/prisma-postgresql/index.js +58 -4
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +85 -0
- package/generated/prisma-postgresql/wasm.js +58 -4
- package/handlers/WEBHOOKS.md +104 -81
- package/handlers/app-definition-loader.js +53 -10
- package/handlers/app-handler-helpers.js +14 -3
- package/handlers/backend-utils.js +0 -1
- package/handlers/database-migration-handler.js +64 -45
- package/handlers/middleware/admin-auth.js +53 -0
- package/handlers/routers/HEALTHCHECK.md +170 -135
- package/handlers/routers/admin.js +474 -0
- package/handlers/routers/auth.js +51 -9
- package/handlers/routers/db-migration.handler.js +2 -3
- package/handlers/routers/db-migration.js +102 -66
- package/handlers/routers/docs.js +180 -0
- package/handlers/routers/health.js +84 -283
- package/handlers/routers/integration-defined-routers.js +44 -33
- package/handlers/routers/integration-webhook-routers.js +73 -52
- package/handlers/routers/middleware/loadUser.js +39 -0
- package/handlers/routers/middleware/requireAdmin.js +43 -0
- package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
- package/handlers/routers/user.js +30 -14
- package/handlers/routers/websocket.js +18 -4
- package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
- package/handlers/workers/db-migration.js +74 -35
- package/handlers/workers/integration-defined-workers.js +31 -20
- package/index.js +50 -16
- package/infrastructure/scheduler/index.js +15 -11
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
- package/integrations/WEBHOOK-QUICKSTART.md +21 -14
- package/integrations/index.js +4 -0
- package/integrations/integration-base.js +9 -6
- package/integrations/integration-router.js +1828 -157
- package/integrations/options.js +47 -20
- package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
- package/integrations/repositories/integration-repository-documentdb.js +53 -24
- package/integrations/repositories/integration-repository-factory.js +6 -6
- package/integrations/repositories/integration-repository-interface.js +27 -9
- package/integrations/repositories/integration-repository-postgres.js +3 -3
- package/integrations/repositories/process-repository-factory.js +4 -7
- package/integrations/repositories/process-repository-interface.js +9 -5
- package/integrations/repositories/process-repository-mongo.js +7 -3
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/config-capturing-integration.js +9 -8
- package/integrations/tests/doubles/dummy-integration-class.js +81 -8
- package/integrations/tests/doubles/test-integration-repository.js +51 -11
- package/integrations/use-cases/create-integration.js +36 -32
- package/integrations/use-cases/create-process.js +20 -9
- package/integrations/use-cases/delete-integration-for-user.js +5 -4
- package/integrations/use-cases/execute-proxy-request.js +564 -0
- package/integrations/use-cases/get-integration-for-user.js +25 -10
- package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
- package/integrations/use-cases/get-integrations-for-user.js +4 -1
- package/integrations/use-cases/get-possible-integrations.js +1 -1
- package/integrations/use-cases/get-process.js +5 -6
- package/integrations/use-cases/index.js +3 -1
- package/integrations/use-cases/load-integration-context.js +1 -5
- package/integrations/use-cases/update-process-state.js +10 -8
- package/integrations/utils/map-integration-dto.js +9 -3
- package/jest-setup.js +1 -1
- package/jest-teardown.js +1 -1
- package/lambda/TimeoutCatcher.js +32 -30
- package/lambda/index.js +1 -1
- package/logs/index.js +3 -3
- package/modules/domain/entities/AuthorizationSession.js +131 -0
- package/modules/domain/entities/index.js +10 -0
- package/modules/module.js +4 -9
- package/modules/repositories/authorization-session-repository-factory.js +53 -0
- package/modules/repositories/authorization-session-repository-interface.js +75 -0
- package/modules/repositories/authorization-session-repository-mongo.js +169 -0
- package/modules/repositories/authorization-session-repository-postgres.js +170 -0
- package/modules/repositories/module-repository-documentdb.js +89 -41
- package/modules/repositories/module-repository-factory.js +4 -6
- package/modules/repositories/module-repository-interface.js +13 -0
- package/modules/repositories/module-repository-mongo.js +40 -5
- package/modules/repositories/module-repository-postgres.js +42 -5
- package/modules/repositories/module-repository.js +28 -0
- package/modules/requester/api-key.js +0 -2
- package/modules/requester/basic.js +0 -1
- package/modules/requester/oauth-2.js +3 -14
- package/modules/test/mock-api/definition.js +15 -10
- package/modules/test/mock-api/mocks/hubspot.js +36 -36
- package/modules/tests/doubles/test-module-factory.js +10 -4
- package/modules/tests/doubles/test-module-repository.js +1 -1
- package/modules/use-cases/delete-module-entity.js +23 -0
- package/modules/use-cases/get-authorization-requirements.js +110 -0
- package/modules/use-cases/get-entities-for-user.js +1 -1
- package/modules/use-cases/get-entity-options-by-id.js +8 -6
- package/modules/use-cases/get-module-entity-by-id.js +16 -0
- package/modules/use-cases/get-module.js +12 -9
- package/modules/use-cases/process-authorization-callback.js +12 -6
- package/modules/use-cases/process-authorization-step.js +160 -0
- package/modules/use-cases/refresh-entity-options.js +8 -6
- package/modules/use-cases/start-authorization-session.js +86 -0
- package/modules/use-cases/test-module-auth.js +8 -6
- package/modules/use-cases/update-module-entity.js +27 -0
- package/modules/utils/map-module-dto.js +2 -2
- package/openapi/openapi-spec-generator.js +370 -0
- package/openapi/openapi-v1.yaml +1360 -0
- package/openapi/openapi-v2.yaml +1535 -0
- package/package.json +7 -9
- package/prisma-mongodb/schema.prisma +87 -0
- package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
- package/prisma-postgresql/schema.prisma +85 -0
- package/providers/index.js +13 -0
- package/providers/resolve-provider.js +93 -0
- package/queues/index.js +19 -0
- package/queues/providers/index.js +7 -0
- package/queues/providers/netlify-background-provider.js +76 -0
- package/queues/providers/qstash-queue-provider.js +122 -0
- package/queues/queue-client-interface.js +55 -0
- package/queues/queue-provider-factory.js +93 -0
- package/queues/queue-provider.js +46 -0
- package/queues/queuer-util.js +33 -25
- package/syncs/repositories/sync-repository-documentdb.js +39 -19
- package/syncs/repositories/sync-repository-factory.js +4 -4
- package/syncs/repositories/sync-repository-interface.js +6 -2
- package/syncs/repositories/sync-repository-postgres.js +19 -13
- package/syncs/sync.js +104 -100
- package/token/repositories/token-repository-factory.js +4 -6
- package/types/assertions/index.d.ts +72 -72
- package/types/core/index.d.ts +98 -53
- package/types/errors/index.d.ts +61 -54
- package/types/eslint-config/index.d.ts +40 -40
- package/types/lambda/index.d.ts +26 -26
- package/types/logs/index.d.ts +4 -4
- package/types/prettier-config/index.d.ts +5 -5
- package/types/test-environment/index.d.ts +14 -14
- package/types/tsconfig.json +95 -93
- package/user/repositories/user-repository-factory.js +4 -4
- package/user/tests/doubles/test-user-repository.js +5 -2
- package/user/use-cases/authenticate-user.js +2 -4
- package/user/use-cases/authenticate-with-shared-secret.js +1 -1
- package/user/use-cases/create-individual-user.js +8 -7
- package/user/use-cases/create-organization-user.js +1 -1
- package/user/use-cases/create-token-for-user-id.js +1 -1
- package/user/use-cases/delete-user.js +47 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
- package/user/use-cases/get-user-from-bearer-token.js +7 -3
- package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
- package/user/use-cases/login-user.js +8 -8
- package/user/user.js +18 -4
- package/utils/backend-path.js +2 -2
- package/utils/index.js +4 -1
- package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
- package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
- package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
- package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
- package/websocket/repositories/websocket-connection-repository.js +26 -20
- package/websocket/websocket-message-sender-interface.js +38 -0
- package/database/adapters/lambda-invoker.js +0 -97
- package/database/repositories/migration-status-repository-s3.js +0 -137
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
|
@@ -4,30 +4,34 @@
|
|
|
4
4
|
* Provides scheduling capabilities for one-time jobs.
|
|
5
5
|
* Follows hexagonal architecture with interface + adapters pattern.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Use createSchedulerService() to get the correct adapter for the
|
|
8
|
+
* active provider. The factory uses resolveProvider() internally.
|
|
9
|
+
*
|
|
10
|
+
* Adapters:
|
|
11
|
+
* - AWS EventBridge: via @friggframework/provider-aws (SchedulerAdapter)
|
|
12
|
+
* - Netlify: NetlifySchedulerAdapter (ships with core, no AWS deps)
|
|
13
|
+
* - Mock: MockSchedulerAdapter (local development)
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
const { SchedulerServiceInterface } = require('./scheduler-service-interface');
|
|
13
|
-
const { EventBridgeSchedulerAdapter } = require('./eventbridge-scheduler-adapter');
|
|
14
|
-
const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
|
|
15
17
|
const {
|
|
16
18
|
createSchedulerService,
|
|
17
19
|
SCHEDULER_PROVIDERS,
|
|
18
|
-
determineProvider,
|
|
19
20
|
} = require('./scheduler-service-factory');
|
|
20
21
|
|
|
21
22
|
module.exports = {
|
|
22
23
|
// Interface (Port)
|
|
23
24
|
SchedulerServiceInterface,
|
|
24
25
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// Core adapters (no external SDK dependencies)
|
|
27
|
+
get MockSchedulerAdapter() {
|
|
28
|
+
return require('./mock-scheduler-adapter').MockSchedulerAdapter;
|
|
29
|
+
},
|
|
30
|
+
get NetlifySchedulerAdapter() {
|
|
31
|
+
return require('./netlify-scheduler-adapter').NetlifySchedulerAdapter;
|
|
32
|
+
},
|
|
28
33
|
|
|
29
|
-
// Factory
|
|
34
|
+
// Factory — resolves the correct adapter via provider plugin system
|
|
30
35
|
createSchedulerService,
|
|
31
36
|
SCHEDULER_PROVIDERS,
|
|
32
|
-
determineProvider,
|
|
33
37
|
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Netlify Scheduler Adapter
|
|
3
|
+
*
|
|
4
|
+
* Implements SchedulerServiceInterface for Netlify deployments.
|
|
5
|
+
*
|
|
6
|
+
* Netlify Scheduled Functions use cron syntax but are defined statically in
|
|
7
|
+
* netlify.toml or via the @netlify/functions schedule() helper. They cannot
|
|
8
|
+
* be created dynamically at runtime like EventBridge Scheduler.
|
|
9
|
+
*
|
|
10
|
+
* Strategy for one-time scheduled jobs on Netlify:
|
|
11
|
+
* 1. Store the schedule in the database (same as mock adapter pattern)
|
|
12
|
+
* 2. A Netlify Scheduled Function runs on a cron interval (e.g., every 5 min)
|
|
13
|
+
* 3. The cron function queries for due schedules and dispatches them
|
|
14
|
+
* to a background function for execution
|
|
15
|
+
*
|
|
16
|
+
* This is a "poll-and-dispatch" pattern vs EventBridge's "push" pattern.
|
|
17
|
+
* Trade-off: slightly less precise timing (up to cron interval delay),
|
|
18
|
+
* but works on any platform without cloud-specific scheduling APIs.
|
|
19
|
+
*
|
|
20
|
+
* For integrations that need the queue provider to dispatch jobs:
|
|
21
|
+
* - queueResourceId maps to the background function URL path
|
|
22
|
+
* - Payload is stored and forwarded when the cron fires
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const { SchedulerServiceInterface } = require('./scheduler-service-interface');
|
|
26
|
+
|
|
27
|
+
class NetlifySchedulerAdapter extends SchedulerServiceInterface {
|
|
28
|
+
/**
|
|
29
|
+
* @param {Object} options
|
|
30
|
+
* @param {Object} options.repository - Repository for persisting schedules
|
|
31
|
+
* Must implement: save(schedule), delete(scheduleName),
|
|
32
|
+
* findByName(scheduleName), findDue(now)
|
|
33
|
+
* @param {Object} [options.queueProvider] - Queue provider for dispatching due jobs
|
|
34
|
+
*/
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
super();
|
|
37
|
+
this.repository = options.repository;
|
|
38
|
+
this.queueProvider = options.queueProvider;
|
|
39
|
+
|
|
40
|
+
if (!this.repository) {
|
|
41
|
+
console.warn(
|
|
42
|
+
'[NetlifyScheduler] No repository provided. ' +
|
|
43
|
+
'Schedules will be stored in memory (lost on function restart). ' +
|
|
44
|
+
'Provide a database-backed repository for production use.'
|
|
45
|
+
);
|
|
46
|
+
this._inMemorySchedules = new Map();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async scheduleOneTime({
|
|
51
|
+
scheduleName,
|
|
52
|
+
scheduleAt,
|
|
53
|
+
queueResourceId,
|
|
54
|
+
payload,
|
|
55
|
+
}) {
|
|
56
|
+
if (!scheduleName) throw new Error('scheduleName is required');
|
|
57
|
+
if (!scheduleAt || !(scheduleAt instanceof Date))
|
|
58
|
+
throw new Error('scheduleAt must be a valid Date object');
|
|
59
|
+
if (!queueResourceId) throw new Error('queueResourceId is required');
|
|
60
|
+
|
|
61
|
+
const scheduleData = {
|
|
62
|
+
scheduleName,
|
|
63
|
+
scheduledAt: scheduleAt.toISOString(),
|
|
64
|
+
queueResourceId,
|
|
65
|
+
payload,
|
|
66
|
+
createdAt: new Date().toISOString(),
|
|
67
|
+
state: 'PENDING',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (this.repository) {
|
|
71
|
+
await this.repository.save(scheduleData);
|
|
72
|
+
} else {
|
|
73
|
+
this._inMemorySchedules.set(scheduleName, scheduleData);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(
|
|
77
|
+
`[NetlifyScheduler] Scheduled: ${scheduleName} for ${scheduleAt.toISOString()}`
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
scheduledJobId: `netlify-schedule-${scheduleName}`,
|
|
82
|
+
scheduledAt: scheduleAt.toISOString(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async deleteSchedule(scheduleName) {
|
|
87
|
+
if (!scheduleName) throw new Error('scheduleName is required');
|
|
88
|
+
|
|
89
|
+
if (this.repository) {
|
|
90
|
+
await this.repository.delete(scheduleName);
|
|
91
|
+
} else {
|
|
92
|
+
this._inMemorySchedules.delete(scheduleName);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(`[NetlifyScheduler] Deleted schedule: ${scheduleName}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getScheduleStatus(scheduleName) {
|
|
99
|
+
if (!scheduleName) throw new Error('scheduleName is required');
|
|
100
|
+
|
|
101
|
+
let schedule;
|
|
102
|
+
if (this.repository) {
|
|
103
|
+
schedule = await this.repository.findByName(scheduleName);
|
|
104
|
+
} else {
|
|
105
|
+
schedule = this._inMemorySchedules.get(scheduleName);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!schedule) {
|
|
109
|
+
return { exists: false };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
exists: true,
|
|
114
|
+
scheduledAt: schedule.scheduledAt,
|
|
115
|
+
state: schedule.state,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Process due schedules. Called by the Netlify cron function.
|
|
121
|
+
*
|
|
122
|
+
* Finds all schedules where scheduledAt <= now, dispatches them
|
|
123
|
+
* to the queue provider, and marks them as completed.
|
|
124
|
+
*
|
|
125
|
+
* @returns {Promise<{ processed: number, errors: number }>}
|
|
126
|
+
*/
|
|
127
|
+
async processDueSchedules() {
|
|
128
|
+
if (!this.repository) {
|
|
129
|
+
console.warn(
|
|
130
|
+
'[NetlifyScheduler] Cannot process due schedules without a repository'
|
|
131
|
+
);
|
|
132
|
+
return { processed: 0, errors: 0 };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const now = new Date();
|
|
136
|
+
const dueSchedules = await this.repository.findDue(now);
|
|
137
|
+
|
|
138
|
+
let processed = 0;
|
|
139
|
+
let errors = 0;
|
|
140
|
+
|
|
141
|
+
for (const schedule of dueSchedules) {
|
|
142
|
+
try {
|
|
143
|
+
// Mark as PROCESSING before dispatch so the next cron tick
|
|
144
|
+
// won't re-pick this schedule (findDue only returns PENDING).
|
|
145
|
+
await this.repository.save({
|
|
146
|
+
...schedule,
|
|
147
|
+
state: 'PROCESSING',
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (this.queueProvider) {
|
|
151
|
+
await this.queueProvider.send(
|
|
152
|
+
schedule.payload,
|
|
153
|
+
schedule.queueResourceId
|
|
154
|
+
);
|
|
155
|
+
} else {
|
|
156
|
+
console.log(
|
|
157
|
+
`[NetlifyScheduler] No queue provider — logging payload for: ${schedule.scheduleName}`,
|
|
158
|
+
JSON.stringify(schedule.payload)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await this.repository.delete(schedule.scheduleName);
|
|
163
|
+
processed++;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error(
|
|
166
|
+
`[NetlifyScheduler] Error processing schedule ${schedule.scheduleName}:`,
|
|
167
|
+
error
|
|
168
|
+
);
|
|
169
|
+
// Mark as FAILED so it won't be re-dispatched automatically.
|
|
170
|
+
// Operators can inspect FAILED schedules and retry manually.
|
|
171
|
+
await this.repository
|
|
172
|
+
.save({ ...schedule, state: 'FAILED' })
|
|
173
|
+
.catch(() => {});
|
|
174
|
+
errors++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(
|
|
179
|
+
`[NetlifyScheduler] Processed ${processed} due schedules, ${errors} errors`
|
|
180
|
+
);
|
|
181
|
+
return { processed, errors };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = { NetlifySchedulerAdapter };
|
|
@@ -4,70 +4,77 @@
|
|
|
4
4
|
* Creates scheduler service instances based on configuration.
|
|
5
5
|
* Returns implementations of SchedulerServiceInterface.
|
|
6
6
|
*
|
|
7
|
+
* Uses the provider plugin system (resolveProvider) to load the correct
|
|
8
|
+
* scheduler adapter for the active platform (AWS, Netlify, etc.).
|
|
9
|
+
*
|
|
7
10
|
* Environment Detection:
|
|
8
|
-
* - SCHEDULER_PROVIDER=eventbridge -> Use AWS EventBridge Scheduler
|
|
9
11
|
* - SCHEDULER_PROVIDER=mock -> Use in-memory mock scheduler
|
|
10
12
|
* - Default in dev/test/local stages -> Mock scheduler
|
|
11
|
-
* -
|
|
13
|
+
* - Otherwise -> Resolved provider's SchedulerAdapter
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
|
-
const {
|
|
15
|
-
const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
|
|
16
|
+
const { resolveProvider } = require('../../providers/resolve-provider');
|
|
16
17
|
|
|
17
18
|
const SCHEDULER_PROVIDERS = {
|
|
18
|
-
EVENTBRIDGE: 'eventbridge',
|
|
19
19
|
MOCK: 'mock',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const LOCAL_STAGES = ['dev', 'test', 'local'];
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Determine
|
|
25
|
+
* Determine if mock scheduler should be used
|
|
26
26
|
*
|
|
27
|
-
* @
|
|
27
|
+
* @param {string} [explicit] - Explicit provider override
|
|
28
|
+
* @returns {boolean}
|
|
28
29
|
*/
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return explicitProvider;
|
|
30
|
+
function shouldUseMock(explicit) {
|
|
31
|
+
if (explicit === SCHEDULER_PROVIDERS.MOCK) {
|
|
32
|
+
return true;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return SCHEDULER_PROVIDERS.MOCK;
|
|
34
|
+
if (!explicit) {
|
|
35
|
+
const stage = process.env.STAGE || 'dev';
|
|
36
|
+
return LOCAL_STAGES.includes(stage);
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
return SCHEDULER_PROVIDERS.EVENTBRIDGE;
|
|
38
|
+
return false;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
/**
|
|
44
42
|
* Create a scheduler service instance
|
|
45
43
|
*
|
|
46
44
|
* @param {Object} options
|
|
47
|
-
* @param {string} options.provider - Scheduler provider ('
|
|
45
|
+
* @param {string} options.provider - Scheduler provider ('mock', or omit for auto-detect)
|
|
48
46
|
* @param {string} options.region - AWS region (for EventBridge)
|
|
49
47
|
* @param {boolean} options.verbose - Verbose logging (for Mock)
|
|
48
|
+
* @param {Object} options.repository - Schedule repository (for Netlify - persists schedules)
|
|
49
|
+
* @param {Object} options.queueProvider - Queue provider (for Netlify - dispatches due jobs)
|
|
50
50
|
* @returns {SchedulerServiceInterface} Implementation of scheduler interface
|
|
51
51
|
*/
|
|
52
52
|
function createSchedulerService(options = {}) {
|
|
53
|
-
const
|
|
53
|
+
const explicit = options.provider || process.env.SCHEDULER_PROVIDER;
|
|
54
|
+
|
|
55
|
+
if (shouldUseMock(explicit)) {
|
|
56
|
+
const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
|
|
57
|
+
return new MockSchedulerAdapter({ verbose: options.verbose });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Use the resolved provider's scheduler adapter.
|
|
61
|
+
// Legacy SCHEDULER_PROVIDER values (eventbridge, netlify) are mapped
|
|
62
|
+
// to provider names for backward compatibility.
|
|
63
|
+
const LEGACY_MAP = { eventbridge: 'aws', netlify: 'netlify' };
|
|
64
|
+
const providerName = LEGACY_MAP[explicit] || explicit;
|
|
65
|
+
const providerOverride = providerName ? { provider: providerName } : {};
|
|
66
|
+
const provider = resolveProvider(null, providerOverride);
|
|
54
67
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
case SCHEDULER_PROVIDERS.MOCK:
|
|
61
|
-
return new MockSchedulerAdapter({
|
|
62
|
-
verbose: options.verbose,
|
|
63
|
-
});
|
|
64
|
-
default:
|
|
65
|
-
throw new Error(`Unknown scheduler provider: ${provider}`);
|
|
68
|
+
const Adapter = provider.SchedulerAdapter;
|
|
69
|
+
if (!Adapter) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Provider '${provider.name}' does not export a SchedulerAdapter`
|
|
72
|
+
);
|
|
66
73
|
}
|
|
74
|
+
return new Adapter(options);
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
module.exports = {
|
|
70
78
|
createSchedulerService,
|
|
71
79
|
SCHEDULER_PROVIDERS,
|
|
72
|
-
determineProvider,
|
|
73
80
|
};
|
|
@@ -14,7 +14,7 @@ class MyIntegration extends IntegrationBase {
|
|
|
14
14
|
modules: {
|
|
15
15
|
myapi: { definition: MyApiDefinition },
|
|
16
16
|
},
|
|
17
|
-
webhooks: true,
|
|
17
|
+
webhooks: true, // ← Add this line
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
```
|
|
@@ -55,10 +55,11 @@ POST /api/my-integration-integration/webhooks/:integrationId
|
|
|
55
55
|
## That's It!
|
|
56
56
|
|
|
57
57
|
The default behavior handles:
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
58
|
+
|
|
59
|
+
- ✅ Receiving webhooks (instant 200 OK response)
|
|
60
|
+
- ✅ Queuing to SQS
|
|
61
|
+
- ✅ Loading your integration with DB and API modules
|
|
62
|
+
- ✅ Calling your `onWebhook` handler
|
|
62
63
|
|
|
63
64
|
## Optional: Custom Signature Verification
|
|
64
65
|
|
|
@@ -85,20 +86,24 @@ async onWebhookReceived({ req, res }) {
|
|
|
85
86
|
## Two Webhook Routes
|
|
86
87
|
|
|
87
88
|
### With Integration ID (Recommended)
|
|
89
|
+
|
|
88
90
|
```
|
|
89
91
|
POST /api/{name}-integration/webhooks/:integrationId
|
|
90
92
|
```
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
93
|
+
|
|
94
|
+
- Full integration loaded in worker
|
|
95
|
+
- Access to DB, config, and API modules
|
|
96
|
+
- Use `this.myapi`, `this.config`, etc.
|
|
94
97
|
|
|
95
98
|
### Without Integration ID
|
|
99
|
+
|
|
96
100
|
```
|
|
97
101
|
POST /api/{name}-integration/webhooks
|
|
98
102
|
```
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
103
|
+
|
|
104
|
+
- Unhydrated integration
|
|
105
|
+
- Useful for system-wide events
|
|
106
|
+
- Limited context
|
|
102
107
|
|
|
103
108
|
## Need Help?
|
|
104
109
|
|
|
@@ -107,6 +112,7 @@ See full documentation: `packages/core/handlers/WEBHOOKS.md`
|
|
|
107
112
|
## Common Patterns
|
|
108
113
|
|
|
109
114
|
### Slack
|
|
115
|
+
|
|
110
116
|
```javascript
|
|
111
117
|
async onWebhookReceived({ req, res }) {
|
|
112
118
|
if (req.body.type === 'url_verification') {
|
|
@@ -117,6 +123,7 @@ async onWebhookReceived({ req, res }) {
|
|
|
117
123
|
```
|
|
118
124
|
|
|
119
125
|
### Stripe
|
|
126
|
+
|
|
120
127
|
```javascript
|
|
121
128
|
async onWebhookReceived({ req, res }) {
|
|
122
129
|
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
|
@@ -131,6 +138,7 @@ async onWebhookReceived({ req, res }) {
|
|
|
131
138
|
```
|
|
132
139
|
|
|
133
140
|
### GitHub
|
|
141
|
+
|
|
134
142
|
```javascript
|
|
135
143
|
async onWebhookReceived({ req, res }) {
|
|
136
144
|
const crypto = require('crypto');
|
|
@@ -139,13 +147,12 @@ async onWebhookReceived({ req, res }) {
|
|
|
139
147
|
.createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET)
|
|
140
148
|
.update(JSON.stringify(req.body))
|
|
141
149
|
.digest('hex');
|
|
142
|
-
|
|
150
|
+
|
|
143
151
|
if (`sha256=${hash}` !== signature) {
|
|
144
152
|
return res.status(401).json({ error: 'Invalid signature' });
|
|
145
153
|
}
|
|
146
|
-
|
|
154
|
+
|
|
147
155
|
await this.queueWebhook({ integrationId: req.params.integrationId, body: req.body });
|
|
148
156
|
res.status(200).json({ received: true });
|
|
149
157
|
}
|
|
150
158
|
```
|
|
151
|
-
|
package/integrations/index.js
CHANGED
|
@@ -10,6 +10,9 @@ const {
|
|
|
10
10
|
const {
|
|
11
11
|
LoadIntegrationContextUseCase,
|
|
12
12
|
} = require('./use-cases/load-integration-context');
|
|
13
|
+
const {
|
|
14
|
+
createProcessRepository,
|
|
15
|
+
} = require('./repositories/process-repository-factory');
|
|
13
16
|
|
|
14
17
|
module.exports = {
|
|
15
18
|
IntegrationBase,
|
|
@@ -18,4 +21,5 @@ module.exports = {
|
|
|
18
21
|
checkRequiredParams,
|
|
19
22
|
getModulesDefinitionFromIntegrationClasses,
|
|
20
23
|
LoadIntegrationContextUseCase,
|
|
24
|
+
createProcessRepository,
|
|
21
25
|
};
|
|
@@ -204,11 +204,11 @@ class IntegrationBase {
|
|
|
204
204
|
/**
|
|
205
205
|
* Returns the modules as object with keys as module names.
|
|
206
206
|
* Uses the keys from Definition.modules to attach modules correctly.
|
|
207
|
-
*
|
|
207
|
+
*
|
|
208
208
|
* Example:
|
|
209
209
|
* Definition.modules = { attio: {...}, quo: { definition: { getName: () => 'quo-attio' } } }
|
|
210
210
|
* Module with getName()='quo-attio' gets attached as this.quo (not this['quo-attio'])
|
|
211
|
-
*
|
|
211
|
+
*
|
|
212
212
|
* @private
|
|
213
213
|
* @param {Array} integrationModules - Array of module instances
|
|
214
214
|
* @returns {Object} The modules object
|
|
@@ -220,13 +220,16 @@ class IntegrationBase {
|
|
|
220
220
|
// e.g., 'quo-attio' → 'quo', 'attio' → 'attio'
|
|
221
221
|
const moduleNameToKey = {};
|
|
222
222
|
if (this.constructor.Definition?.modules) {
|
|
223
|
-
for (const [key, moduleConfig] of Object.entries(
|
|
223
|
+
for (const [key, moduleConfig] of Object.entries(
|
|
224
|
+
this.constructor.Definition.modules
|
|
225
|
+
)) {
|
|
224
226
|
const definition = moduleConfig.definition;
|
|
225
227
|
if (definition) {
|
|
226
228
|
// Use getName() if available, fallback to moduleName
|
|
227
|
-
const definitionName =
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
const definitionName =
|
|
230
|
+
typeof definition.getName === 'function'
|
|
231
|
+
? definition.getName()
|
|
232
|
+
: definition.moduleName;
|
|
230
233
|
if (definitionName) {
|
|
231
234
|
moduleNameToKey[definitionName] = key;
|
|
232
235
|
}
|