@friggframework/core 2.0.0--canary.522.893db5d.0 → 2.0.0--canary.545.35013d9.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/generated/prisma-mongodb/edge.js +16 -4
- package/generated/prisma-mongodb/index-browser.js +13 -1
- package/generated/prisma-mongodb/index.d.ts +1342 -103
- package/generated/prisma-mongodb/index.js +16 -4
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +18 -0
- package/generated/prisma-mongodb/wasm.js +16 -4
- package/generated/prisma-postgresql/edge.js +16 -4
- package/generated/prisma-postgresql/index-browser.js +13 -1
- package/generated/prisma-postgresql/index.d.ts +1446 -93
- package/generated/prisma-postgresql/index.js +16 -4
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +17 -0
- package/generated/prisma-postgresql/wasm.js +16 -4
- package/index.js +9 -1
- package/infrastructure/scheduler/index.js +4 -1
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +173 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +10 -1
- package/package.json +5 -5
- package/prisma-mongodb/schema.prisma +18 -0
- package/prisma-postgresql/schema.prisma +17 -0
- package/queues/index.js +19 -0
- package/queues/providers/index.js +9 -0
- package/queues/providers/netlify-background-provider.js +76 -0
- package/queues/providers/qstash-queue-provider.js +122 -0
- package/queues/providers/sqs-queue-provider.js +84 -0
- package/queues/queue-provider-factory.js +84 -0
- package/queues/queue-provider.js +46 -0
|
@@ -241,6 +241,17 @@ exports.Prisma.ScriptScheduleScalarFieldEnum = {
|
|
|
241
241
|
updatedAt: 'updatedAt'
|
|
242
242
|
};
|
|
243
243
|
|
|
244
|
+
exports.Prisma.ScheduledJobScalarFieldEnum = {
|
|
245
|
+
id: 'id',
|
|
246
|
+
scheduleName: 'scheduleName',
|
|
247
|
+
scheduledAt: 'scheduledAt',
|
|
248
|
+
queueResourceId: 'queueResourceId',
|
|
249
|
+
payload: 'payload',
|
|
250
|
+
state: 'state',
|
|
251
|
+
createdAt: 'createdAt',
|
|
252
|
+
updatedAt: 'updatedAt'
|
|
253
|
+
};
|
|
254
|
+
|
|
244
255
|
exports.Prisma.SortOrder = {
|
|
245
256
|
asc: 'asc',
|
|
246
257
|
desc: 'desc'
|
|
@@ -318,7 +329,8 @@ exports.Prisma.ModelName = {
|
|
|
318
329
|
State: 'State',
|
|
319
330
|
WebsocketConnection: 'WebsocketConnection',
|
|
320
331
|
AdminProcess: 'AdminProcess',
|
|
321
|
-
ScriptSchedule: 'ScriptSchedule'
|
|
332
|
+
ScriptSchedule: 'ScriptSchedule',
|
|
333
|
+
ScheduledJob: 'ScheduledJob'
|
|
322
334
|
};
|
|
323
335
|
/**
|
|
324
336
|
* Create the Client
|
|
@@ -372,13 +384,13 @@ const config = {
|
|
|
372
384
|
}
|
|
373
385
|
}
|
|
374
386
|
},
|
|
375
|
-
"inlineSchema": "// Frigg Framework - Prisma Schema (PostgreSQL)\n// PostgreSQL database schema for enterprise integration platform\n// Converted from MongoDB schema for relational database support\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma-postgresql\"\n binaryTargets = [\"native\", \"rhel-openssl-3.0.x\"] // native for local dev, rhel for Lambda deployment\n engineType = \"binary\" // Use binary engines (smaller size)\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\n// ============================================================================\n// USER MODELS\n// ============================================================================\n\n/// User model with discriminator pattern support\n/// Replaces Mongoose discriminators (IndividualUser, OrganizationUser)\nmodel User {\n id Int @id @default(autoincrement())\n type UserType\n\n // Timestamps\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // IndividualUser fields (nullable for organizations)\n email String?\n username String?\n hashword String? // Bcrypt hashed password (handled in application layer)\n appUserId String?\n organizationId Int?\n\n // Self-referential relation for organization membership\n organization User? @relation(\"OrgMembers\", fields: [organizationId], references: [id], onDelete: NoAction, onUpdate: NoAction)\n members User[] @relation(\"OrgMembers\")\n\n // OrganizationUser fields (nullable for individuals)\n appOrgId String?\n name String?\n\n // Relations\n tokens Token[]\n credentials Credential[]\n entities Entity[]\n integrations Integration[]\n processes Process[]\n\n @@unique([username, appUserId])\n @@index([type])\n @@index([appUserId])\n}\n\nenum UserType {\n INDIVIDUAL\n ORGANIZATION\n}\n\n// ============================================================================\n// AUTHENTICATION MODELS\n// ============================================================================\n\n/// Authentication tokens with expiration\n/// Bcrypt hashed tokens stored (handled in application layer)\nmodel Token {\n id Int @id @default(autoincrement())\n token String // Bcrypt hashed\n created DateTime @default(now())\n expires DateTime?\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@index([userId])\n @@index([expires])\n}\n\n// ============================================================================\n// CREDENTIAL & ENTITY MODELS\n// ============================================================================\n\n/// OAuth credentials and API tokens\n/// All sensitive data encrypted with KMS at rest\nmodel Credential {\n id Int @id @default(autoincrement())\n userId Int?\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n authIsValid Boolean?\n externalId String?\n\n // Dynamic OAuth fields stored as JSON (encrypted via Prisma middleware)\n // Contains: access_token, refresh_token, domain, expires_in, token_type, etc.\n data Json @default(\"{}\")\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // Relations\n entities Entity[]\n\n @@index([userId])\n @@index([externalId])\n}\n\n/// External service entities (API connections)\nmodel Entity {\n id Int @id @default(autoincrement())\n credentialId Int?\n credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull)\n userId Int?\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n name String?\n moduleName String?\n externalId String?\n\n data Json @default(\"{}\")\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // Relations - many-to-many with implicit join tables\n integrations Integration[]\n syncs Sync[]\n\n dataIdentifiers DataIdentifier[]\n associationObjects AssociationObject[]\n\n @@index([userId])\n @@index([externalId])\n @@index([moduleName])\n @@index([credentialId])\n}\n\n// ============================================================================\n// INTEGRATION MODELS\n// ============================================================================\n\n/// Main integration configuration and state\nmodel Integration {\n id Int @id @default(autoincrement())\n userId Int?\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n status IntegrationStatus @default(ENABLED)\n\n // Configuration and version\n config Json? // Integration configuration object\n version String?\n\n // Entity references (many-to-many via implicit join table)\n entities Entity[]\n\n // Message arrays (stored as JSON)\n errors Json @default(\"[]\")\n warnings Json @default(\"[]\")\n info Json @default(\"[]\")\n logs Json @default(\"[]\")\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // Relations\n associations Association[]\n syncs Sync[]\n mappings IntegrationMapping[]\n processes Process[]\n\n @@index([userId])\n @@index([status])\n}\n\nenum IntegrationStatus {\n ENABLED\n NEEDS_CONFIG\n PROCESSING\n DISABLED\n ERROR\n}\n\n/// Integration-specific data mappings\n/// All mapping data encrypted with KMS\nmodel IntegrationMapping {\n id Int @id @default(autoincrement())\n integrationId Int\n integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)\n sourceId String?\n\n // Encrypted mapping data (handled via Prisma middleware)\n mapping Json?\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@unique([integrationId, sourceId])\n @@index([integrationId])\n @@index([sourceId])\n}\n\n// ============================================================================\n// SYNC MODELS\n// ============================================================================\n\n/// Bidirectional data synchronization tracking\nmodel Sync {\n id Int @id @default(autoincrement())\n integrationId Int?\n integration Integration? @relation(fields: [integrationId], references: [id], onDelete: Cascade)\n\n // Entity references (many-to-many via implicit join table)\n entities Entity[]\n\n hash String\n name String\n\n // Data identifiers (extracted to separate model)\n dataIdentifiers DataIdentifier[]\n\n @@index([integrationId])\n @@index([hash])\n @@index([name])\n}\n\n/// Data identifier for sync operations\n/// Replaces nested array structure in Mongoose\nmodel DataIdentifier {\n id Int @id @default(autoincrement())\n syncId Int?\n sync Sync? @relation(fields: [syncId], references: [id], onDelete: Cascade)\n entityId Int\n entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)\n\n // Identifier data (can be any structure)\n idData Json\n\n hash String\n\n @@index([syncId])\n @@index([entityId])\n @@index([hash])\n}\n\n// ============================================================================\n// ASSOCIATION MODELS\n// ============================================================================\n\n/// Entity associations with cardinality tracking\nmodel Association {\n id Int @id @default(autoincrement())\n integrationId Int\n integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)\n name String\n type AssociationType\n primaryObject String\n\n // Associated objects (extracted to separate model)\n objects AssociationObject[]\n\n @@index([integrationId])\n @@index([name])\n}\n\n/// Association object entry\n/// Replaces nested array structure in Mongoose\nmodel AssociationObject {\n id Int @id @default(autoincrement())\n associationId Int\n association Association @relation(fields: [associationId], references: [id], onDelete: Cascade)\n entityId Int\n entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)\n objectType String\n objId String\n metadata Json? // Optional metadata\n\n @@index([associationId])\n @@index([entityId])\n}\n\nenum AssociationType {\n ONE_TO_MANY\n ONE_TO_ONE\n MANY_TO_ONE\n}\n\n// ============================================================================\n// PROCESS MODELS\n// ============================================================================\n\n/// Generic Process Model - tracks any long-running operation\n/// Used for: CRM syncs, data migrations, bulk operations, etc.\nmodel Process {\n id Int @id @default(autoincrement())\n\n // Core references\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n integrationId Int\n integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)\n\n // Process identification\n name String // e.g., \"zoho-crm-contact-sync\", \"pipedrive-lead-sync\"\n type String // e.g., \"CRM_SYNC\", \"DATA_MIGRATION\", \"BULK_OPERATION\"\n\n // State machine\n state String // Current state (integration-defined states)\n\n // Flexible storage\n context Json @default(\"{}\") // Process-specific data (pagination, metadata, etc.)\n results Json @default(\"{}\") // Process results and metrics\n\n // Hierarchy support - self-referential relation\n parentProcessId Int?\n parentProcess Process? @relation(\"ProcessHierarchy\", fields: [parentProcessId], references: [id], onDelete: SetNull)\n childProcesses Process[] @relation(\"ProcessHierarchy\")\n\n // Timestamps\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId])\n @@index([integrationId])\n @@index([type])\n @@index([state])\n @@index([name])\n @@index([parentProcessId])\n}\n\n// ============================================================================\n// UTILITY MODELS\n// ============================================================================\n\n/// Generic state storage\nmodel State {\n id Int @id @default(autoincrement())\n state Json?\n}\n\n/// AWS API Gateway WebSocket connection tracking\nmodel WebsocketConnection {\n id Int @id @default(autoincrement())\n connectionId String?\n\n @@index([connectionId])\n}\n\n// ============================================================================\n// ADMIN PROCESS MODELS\n// ============================================================================\n\n/// Admin process state machine\nenum AdminProcessState {\n PENDING\n RUNNING\n COMPLETED\n FAILED\n}\n\n/// Admin trigger types\nenum AdminTrigger {\n MANUAL\n SCHEDULED\n QUEUE\n WEBHOOK\n}\n\n/// Admin process tracking (like Process but without user/integration FK)\n/// Used for: admin scripts, db migrations, system maintenance tasks\nmodel AdminProcess {\n id Int @id @default(autoincrement())\n name String // e.g., \"oauth-token-refresh\", \"db-migration-xyz\"\n type String // e.g., \"ADMIN_SCRIPT\", \"DB_MIGRATION\"\n\n // State machine\n state AdminProcessState @default(PENDING)\n\n // Flexible storage (mirrors Process model pattern)\n context Json @default(\"{}\") // input, trigger, audit info, script version\n results Json @default(\"{}\") // output, logs, metrics, errors\n\n // Hierarchy support - self-referential relation\n parentProcessId Int?\n parentProcess AdminProcess? @relation(\"AdminProcessHierarchy\", fields: [parentProcessId], references: [id], onDelete: SetNull)\n childProcesses AdminProcess[] @relation(\"AdminProcessHierarchy\")\n\n // Timestamps\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([name, createdAt(sort: Desc)])\n @@index([state])\n @@index([type])\n}\n\n/// Script scheduling configuration for hybrid scheduling (SQS + EventBridge)\nmodel ScriptSchedule {\n id Int @id @default(autoincrement())\n scriptName String @unique\n enabled Boolean @default(false)\n cronExpression String?\n timezone String @default(\"UTC\")\n lastTriggeredAt DateTime?\n nextTriggerAt DateTime?\n\n // External Scheduler (e.g., AWS EventBridge Scheduler)\n externalScheduleId String?\n externalScheduleName String?\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([enabled])\n}\n",
|
|
376
|
-
"inlineSchemaHash": "
|
|
387
|
+
"inlineSchema": "// Frigg Framework - Prisma Schema (PostgreSQL)\n// PostgreSQL database schema for enterprise integration platform\n// Converted from MongoDB schema for relational database support\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma-postgresql\"\n binaryTargets = [\"native\", \"rhel-openssl-3.0.x\"] // native for local dev, rhel for Lambda deployment\n engineType = \"binary\" // Use binary engines (smaller size)\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\n// ============================================================================\n// USER MODELS\n// ============================================================================\n\n/// User model with discriminator pattern support\n/// Replaces Mongoose discriminators (IndividualUser, OrganizationUser)\nmodel User {\n id Int @id @default(autoincrement())\n type UserType\n\n // Timestamps\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // IndividualUser fields (nullable for organizations)\n email String?\n username String?\n hashword String? // Bcrypt hashed password (handled in application layer)\n appUserId String?\n organizationId Int?\n\n // Self-referential relation for organization membership\n organization User? @relation(\"OrgMembers\", fields: [organizationId], references: [id], onDelete: NoAction, onUpdate: NoAction)\n members User[] @relation(\"OrgMembers\")\n\n // OrganizationUser fields (nullable for individuals)\n appOrgId String?\n name String?\n\n // Relations\n tokens Token[]\n credentials Credential[]\n entities Entity[]\n integrations Integration[]\n processes Process[]\n\n @@unique([username, appUserId])\n @@index([type])\n @@index([appUserId])\n}\n\nenum UserType {\n INDIVIDUAL\n ORGANIZATION\n}\n\n// ============================================================================\n// AUTHENTICATION MODELS\n// ============================================================================\n\n/// Authentication tokens with expiration\n/// Bcrypt hashed tokens stored (handled in application layer)\nmodel Token {\n id Int @id @default(autoincrement())\n token String // Bcrypt hashed\n created DateTime @default(now())\n expires DateTime?\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@index([userId])\n @@index([expires])\n}\n\n// ============================================================================\n// CREDENTIAL & ENTITY MODELS\n// ============================================================================\n\n/// OAuth credentials and API tokens\n/// All sensitive data encrypted with KMS at rest\nmodel Credential {\n id Int @id @default(autoincrement())\n userId Int?\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n authIsValid Boolean?\n externalId String?\n\n // Dynamic OAuth fields stored as JSON (encrypted via Prisma middleware)\n // Contains: access_token, refresh_token, domain, expires_in, token_type, etc.\n data Json @default(\"{}\")\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // Relations\n entities Entity[]\n\n @@index([userId])\n @@index([externalId])\n}\n\n/// External service entities (API connections)\nmodel Entity {\n id Int @id @default(autoincrement())\n credentialId Int?\n credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull)\n userId Int?\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n name String?\n moduleName String?\n externalId String?\n\n data Json @default(\"{}\")\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // Relations - many-to-many with implicit join tables\n integrations Integration[]\n syncs Sync[]\n\n dataIdentifiers DataIdentifier[]\n associationObjects AssociationObject[]\n\n @@index([userId])\n @@index([externalId])\n @@index([moduleName])\n @@index([credentialId])\n}\n\n// ============================================================================\n// INTEGRATION MODELS\n// ============================================================================\n\n/// Main integration configuration and state\nmodel Integration {\n id Int @id @default(autoincrement())\n userId Int?\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n status IntegrationStatus @default(ENABLED)\n\n // Configuration and version\n config Json? // Integration configuration object\n version String?\n\n // Entity references (many-to-many via implicit join table)\n entities Entity[]\n\n // Message arrays (stored as JSON)\n errors Json @default(\"[]\")\n warnings Json @default(\"[]\")\n info Json @default(\"[]\")\n logs Json @default(\"[]\")\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // Relations\n associations Association[]\n syncs Sync[]\n mappings IntegrationMapping[]\n processes Process[]\n\n @@index([userId])\n @@index([status])\n}\n\nenum IntegrationStatus {\n ENABLED\n NEEDS_CONFIG\n PROCESSING\n DISABLED\n ERROR\n}\n\n/// Integration-specific data mappings\n/// All mapping data encrypted with KMS\nmodel IntegrationMapping {\n id Int @id @default(autoincrement())\n integrationId Int\n integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)\n sourceId String?\n\n // Encrypted mapping data (handled via Prisma middleware)\n mapping Json?\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@unique([integrationId, sourceId])\n @@index([integrationId])\n @@index([sourceId])\n}\n\n// ============================================================================\n// SYNC MODELS\n// ============================================================================\n\n/// Bidirectional data synchronization tracking\nmodel Sync {\n id Int @id @default(autoincrement())\n integrationId Int?\n integration Integration? @relation(fields: [integrationId], references: [id], onDelete: Cascade)\n\n // Entity references (many-to-many via implicit join table)\n entities Entity[]\n\n hash String\n name String\n\n // Data identifiers (extracted to separate model)\n dataIdentifiers DataIdentifier[]\n\n @@index([integrationId])\n @@index([hash])\n @@index([name])\n}\n\n/// Data identifier for sync operations\n/// Replaces nested array structure in Mongoose\nmodel DataIdentifier {\n id Int @id @default(autoincrement())\n syncId Int?\n sync Sync? @relation(fields: [syncId], references: [id], onDelete: Cascade)\n entityId Int\n entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)\n\n // Identifier data (can be any structure)\n idData Json\n\n hash String\n\n @@index([syncId])\n @@index([entityId])\n @@index([hash])\n}\n\n// ============================================================================\n// ASSOCIATION MODELS\n// ============================================================================\n\n/// Entity associations with cardinality tracking\nmodel Association {\n id Int @id @default(autoincrement())\n integrationId Int\n integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)\n name String\n type AssociationType\n primaryObject String\n\n // Associated objects (extracted to separate model)\n objects AssociationObject[]\n\n @@index([integrationId])\n @@index([name])\n}\n\n/// Association object entry\n/// Replaces nested array structure in Mongoose\nmodel AssociationObject {\n id Int @id @default(autoincrement())\n associationId Int\n association Association @relation(fields: [associationId], references: [id], onDelete: Cascade)\n entityId Int\n entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)\n objectType String\n objId String\n metadata Json? // Optional metadata\n\n @@index([associationId])\n @@index([entityId])\n}\n\nenum AssociationType {\n ONE_TO_MANY\n ONE_TO_ONE\n MANY_TO_ONE\n}\n\n// ============================================================================\n// PROCESS MODELS\n// ============================================================================\n\n/// Generic Process Model - tracks any long-running operation\n/// Used for: CRM syncs, data migrations, bulk operations, etc.\nmodel Process {\n id Int @id @default(autoincrement())\n\n // Core references\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n integrationId Int\n integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)\n\n // Process identification\n name String // e.g., \"zoho-crm-contact-sync\", \"pipedrive-lead-sync\"\n type String // e.g., \"CRM_SYNC\", \"DATA_MIGRATION\", \"BULK_OPERATION\"\n\n // State machine\n state String // Current state (integration-defined states)\n\n // Flexible storage\n context Json @default(\"{}\") // Process-specific data (pagination, metadata, etc.)\n results Json @default(\"{}\") // Process results and metrics\n\n // Hierarchy support - self-referential relation\n parentProcessId Int?\n parentProcess Process? @relation(\"ProcessHierarchy\", fields: [parentProcessId], references: [id], onDelete: SetNull)\n childProcesses Process[] @relation(\"ProcessHierarchy\")\n\n // Timestamps\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId])\n @@index([integrationId])\n @@index([type])\n @@index([state])\n @@index([name])\n @@index([parentProcessId])\n}\n\n// ============================================================================\n// UTILITY MODELS\n// ============================================================================\n\n/// Generic state storage\nmodel State {\n id Int @id @default(autoincrement())\n state Json?\n}\n\n/// AWS API Gateway WebSocket connection tracking\nmodel WebsocketConnection {\n id Int @id @default(autoincrement())\n connectionId String?\n\n @@index([connectionId])\n}\n\n// ============================================================================\n// ADMIN PROCESS MODELS\n// ============================================================================\n\n/// Admin process state machine\nenum AdminProcessState {\n PENDING\n RUNNING\n COMPLETED\n FAILED\n}\n\n/// Admin trigger types\nenum AdminTrigger {\n MANUAL\n SCHEDULED\n QUEUE\n WEBHOOK\n}\n\n/// Admin process tracking (like Process but without user/integration FK)\n/// Used for: admin scripts, db migrations, system maintenance tasks\nmodel AdminProcess {\n id Int @id @default(autoincrement())\n name String // e.g., \"oauth-token-refresh\", \"db-migration-xyz\"\n type String // e.g., \"ADMIN_SCRIPT\", \"DB_MIGRATION\"\n\n // State machine\n state AdminProcessState @default(PENDING)\n\n // Flexible storage (mirrors Process model pattern)\n context Json @default(\"{}\") // input, trigger, audit info, script version\n results Json @default(\"{}\") // output, logs, metrics, errors\n\n // Hierarchy support - self-referential relation\n parentProcessId Int?\n parentProcess AdminProcess? @relation(\"AdminProcessHierarchy\", fields: [parentProcessId], references: [id], onDelete: SetNull)\n childProcesses AdminProcess[] @relation(\"AdminProcessHierarchy\")\n\n // Timestamps\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([name, createdAt(sort: Desc)])\n @@index([state])\n @@index([type])\n}\n\n/// Script scheduling configuration for hybrid scheduling (SQS + EventBridge)\nmodel ScriptSchedule {\n id Int @id @default(autoincrement())\n scriptName String @unique\n enabled Boolean @default(false)\n cronExpression String?\n timezone String @default(\"UTC\")\n lastTriggeredAt DateTime?\n nextTriggerAt DateTime?\n\n // External Scheduler (e.g., AWS EventBridge Scheduler)\n externalScheduleId String?\n externalScheduleName String?\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([enabled])\n}\n\n/// One-time scheduled jobs for poll-and-dispatch scheduling pattern.\n/// Used by providers without native one-time scheduling APIs (e.g., Netlify).\n/// A cron function queries for due jobs and dispatches them to a queue.\nmodel ScheduledJob {\n id Int @id @default(autoincrement())\n scheduleName String @unique\n scheduledAt DateTime\n queueResourceId String\n payload Json?\n state String @default(\"PENDING\")\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([state, scheduledAt])\n}\n",
|
|
388
|
+
"inlineSchemaHash": "2052aad2c358861edccdc2e53b023f217e9d8629d658000b098cbc85fbef4551",
|
|
377
389
|
"copyEngine": true
|
|
378
390
|
}
|
|
379
391
|
config.dirname = '/'
|
|
380
392
|
|
|
381
|
-
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"type\",\"kind\":\"enum\",\"type\":\"UserType\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"hashword\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appUserId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"organizationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"organization\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"OrgMembers\"},{\"name\":\"members\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"OrgMembers\"},{\"name\":\"appOrgId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"tokens\",\"kind\":\"object\",\"type\":\"Token\",\"relationName\":\"TokenToUser\"},{\"name\":\"credentials\",\"kind\":\"object\",\"type\":\"Credential\",\"relationName\":\"CredentialToUser\"},{\"name\":\"entities\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"EntityToUser\"},{\"name\":\"integrations\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"IntegrationToUser\"},{\"name\":\"processes\",\"kind\":\"object\",\"type\":\"Process\",\"relationName\":\"ProcessToUser\"}],\"dbName\":null},\"Token\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"created\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"expires\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TokenToUser\"}],\"dbName\":null},\"Credential\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CredentialToUser\"},{\"name\":\"authIsValid\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"externalId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"entities\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"CredentialToEntity\"}],\"dbName\":null},\"Entity\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"credentialId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"credential\",\"kind\":\"object\",\"type\":\"Credential\",\"relationName\":\"CredentialToEntity\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"EntityToUser\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"moduleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"externalId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"integrations\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"EntityToIntegration\"},{\"name\":\"syncs\",\"kind\":\"object\",\"type\":\"Sync\",\"relationName\":\"EntityToSync\"},{\"name\":\"dataIdentifiers\",\"kind\":\"object\",\"type\":\"DataIdentifier\",\"relationName\":\"DataIdentifierToEntity\"},{\"name\":\"associationObjects\",\"kind\":\"object\",\"type\":\"AssociationObject\",\"relationName\":\"AssociationObjectToEntity\"}],\"dbName\":null},\"Integration\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"IntegrationToUser\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"IntegrationStatus\"},{\"name\":\"config\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"version\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"entities\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"EntityToIntegration\"},{\"name\":\"errors\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"warnings\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"info\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"logs\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"associations\",\"kind\":\"object\",\"type\":\"Association\",\"relationName\":\"AssociationToIntegration\"},{\"name\":\"syncs\",\"kind\":\"object\",\"type\":\"Sync\",\"relationName\":\"IntegrationToSync\"},{\"name\":\"mappings\",\"kind\":\"object\",\"type\":\"IntegrationMapping\",\"relationName\":\"IntegrationToIntegrationMapping\"},{\"name\":\"processes\",\"kind\":\"object\",\"type\":\"Process\",\"relationName\":\"IntegrationToProcess\"}],\"dbName\":null},\"IntegrationMapping\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integrationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integration\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"IntegrationToIntegrationMapping\"},{\"name\":\"sourceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"mapping\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Sync\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integrationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integration\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"IntegrationToSync\"},{\"name\":\"entities\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"EntityToSync\"},{\"name\":\"hash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"dataIdentifiers\",\"kind\":\"object\",\"type\":\"DataIdentifier\",\"relationName\":\"DataIdentifierToSync\"}],\"dbName\":null},\"DataIdentifier\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"syncId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"sync\",\"kind\":\"object\",\"type\":\"Sync\",\"relationName\":\"DataIdentifierToSync\"},{\"name\":\"entityId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"entity\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"DataIdentifierToEntity\"},{\"name\":\"idData\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"hash\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Association\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integrationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integration\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"AssociationToIntegration\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"enum\",\"type\":\"AssociationType\"},{\"name\":\"primaryObject\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"objects\",\"kind\":\"object\",\"type\":\"AssociationObject\",\"relationName\":\"AssociationToAssociationObject\"}],\"dbName\":null},\"AssociationObject\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"associationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"association\",\"kind\":\"object\",\"type\":\"Association\",\"relationName\":\"AssociationToAssociationObject\"},{\"name\":\"entityId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"entity\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"AssociationObjectToEntity\"},{\"name\":\"objectType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"objId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"metadata\",\"kind\":\"scalar\",\"type\":\"Json\"}],\"dbName\":null},\"Process\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ProcessToUser\"},{\"name\":\"integrationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integration\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"IntegrationToProcess\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"state\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"context\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"results\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"parentProcessId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parentProcess\",\"kind\":\"object\",\"type\":\"Process\",\"relationName\":\"ProcessHierarchy\"},{\"name\":\"childProcesses\",\"kind\":\"object\",\"type\":\"Process\",\"relationName\":\"ProcessHierarchy\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"State\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"state\",\"kind\":\"scalar\",\"type\":\"Json\"}],\"dbName\":null},\"WebsocketConnection\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"connectionId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"AdminProcess\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"state\",\"kind\":\"enum\",\"type\":\"AdminProcessState\"},{\"name\":\"context\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"results\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"parentProcessId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parentProcess\",\"kind\":\"object\",\"type\":\"AdminProcess\",\"relationName\":\"AdminProcessHierarchy\"},{\"name\":\"childProcesses\",\"kind\":\"object\",\"type\":\"AdminProcess\",\"relationName\":\"AdminProcessHierarchy\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ScriptSchedule\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"scriptName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"cronExpression\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"timezone\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastTriggeredAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"nextTriggerAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"externalScheduleId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"externalScheduleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
|
393
|
+
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"type\",\"kind\":\"enum\",\"type\":\"UserType\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"hashword\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appUserId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"organizationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"organization\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"OrgMembers\"},{\"name\":\"members\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"OrgMembers\"},{\"name\":\"appOrgId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"tokens\",\"kind\":\"object\",\"type\":\"Token\",\"relationName\":\"TokenToUser\"},{\"name\":\"credentials\",\"kind\":\"object\",\"type\":\"Credential\",\"relationName\":\"CredentialToUser\"},{\"name\":\"entities\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"EntityToUser\"},{\"name\":\"integrations\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"IntegrationToUser\"},{\"name\":\"processes\",\"kind\":\"object\",\"type\":\"Process\",\"relationName\":\"ProcessToUser\"}],\"dbName\":null},\"Token\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"created\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"expires\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TokenToUser\"}],\"dbName\":null},\"Credential\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CredentialToUser\"},{\"name\":\"authIsValid\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"externalId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"entities\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"CredentialToEntity\"}],\"dbName\":null},\"Entity\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"credentialId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"credential\",\"kind\":\"object\",\"type\":\"Credential\",\"relationName\":\"CredentialToEntity\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"EntityToUser\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"moduleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"externalId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"integrations\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"EntityToIntegration\"},{\"name\":\"syncs\",\"kind\":\"object\",\"type\":\"Sync\",\"relationName\":\"EntityToSync\"},{\"name\":\"dataIdentifiers\",\"kind\":\"object\",\"type\":\"DataIdentifier\",\"relationName\":\"DataIdentifierToEntity\"},{\"name\":\"associationObjects\",\"kind\":\"object\",\"type\":\"AssociationObject\",\"relationName\":\"AssociationObjectToEntity\"}],\"dbName\":null},\"Integration\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"IntegrationToUser\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"IntegrationStatus\"},{\"name\":\"config\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"version\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"entities\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"EntityToIntegration\"},{\"name\":\"errors\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"warnings\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"info\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"logs\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"associations\",\"kind\":\"object\",\"type\":\"Association\",\"relationName\":\"AssociationToIntegration\"},{\"name\":\"syncs\",\"kind\":\"object\",\"type\":\"Sync\",\"relationName\":\"IntegrationToSync\"},{\"name\":\"mappings\",\"kind\":\"object\",\"type\":\"IntegrationMapping\",\"relationName\":\"IntegrationToIntegrationMapping\"},{\"name\":\"processes\",\"kind\":\"object\",\"type\":\"Process\",\"relationName\":\"IntegrationToProcess\"}],\"dbName\":null},\"IntegrationMapping\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integrationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integration\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"IntegrationToIntegrationMapping\"},{\"name\":\"sourceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"mapping\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Sync\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integrationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integration\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"IntegrationToSync\"},{\"name\":\"entities\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"EntityToSync\"},{\"name\":\"hash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"dataIdentifiers\",\"kind\":\"object\",\"type\":\"DataIdentifier\",\"relationName\":\"DataIdentifierToSync\"}],\"dbName\":null},\"DataIdentifier\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"syncId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"sync\",\"kind\":\"object\",\"type\":\"Sync\",\"relationName\":\"DataIdentifierToSync\"},{\"name\":\"entityId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"entity\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"DataIdentifierToEntity\"},{\"name\":\"idData\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"hash\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Association\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integrationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integration\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"AssociationToIntegration\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"enum\",\"type\":\"AssociationType\"},{\"name\":\"primaryObject\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"objects\",\"kind\":\"object\",\"type\":\"AssociationObject\",\"relationName\":\"AssociationToAssociationObject\"}],\"dbName\":null},\"AssociationObject\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"associationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"association\",\"kind\":\"object\",\"type\":\"Association\",\"relationName\":\"AssociationToAssociationObject\"},{\"name\":\"entityId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"entity\",\"kind\":\"object\",\"type\":\"Entity\",\"relationName\":\"AssociationObjectToEntity\"},{\"name\":\"objectType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"objId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"metadata\",\"kind\":\"scalar\",\"type\":\"Json\"}],\"dbName\":null},\"Process\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ProcessToUser\"},{\"name\":\"integrationId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"integration\",\"kind\":\"object\",\"type\":\"Integration\",\"relationName\":\"IntegrationToProcess\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"state\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"context\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"results\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"parentProcessId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parentProcess\",\"kind\":\"object\",\"type\":\"Process\",\"relationName\":\"ProcessHierarchy\"},{\"name\":\"childProcesses\",\"kind\":\"object\",\"type\":\"Process\",\"relationName\":\"ProcessHierarchy\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"State\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"state\",\"kind\":\"scalar\",\"type\":\"Json\"}],\"dbName\":null},\"WebsocketConnection\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"connectionId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"AdminProcess\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"state\",\"kind\":\"enum\",\"type\":\"AdminProcessState\"},{\"name\":\"context\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"results\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"parentProcessId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parentProcess\",\"kind\":\"object\",\"type\":\"AdminProcess\",\"relationName\":\"AdminProcessHierarchy\"},{\"name\":\"childProcesses\",\"kind\":\"object\",\"type\":\"AdminProcess\",\"relationName\":\"AdminProcessHierarchy\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ScriptSchedule\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"scriptName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"cronExpression\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"timezone\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastTriggeredAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"nextTriggerAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"externalScheduleId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"externalScheduleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ScheduledJob\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"scheduleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"scheduledAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"queueResourceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"payload\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"state\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
|
382
394
|
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
|
|
383
395
|
config.engineWasm = {
|
|
384
396
|
getRuntime: async () => require('./query_engine_bg.js'),
|
package/index.js
CHANGED
|
@@ -92,7 +92,12 @@ const utils = require('./utils');
|
|
|
92
92
|
|
|
93
93
|
// const {Sync } = require('./syncs/model');
|
|
94
94
|
|
|
95
|
-
const {
|
|
95
|
+
const {
|
|
96
|
+
QueuerUtil,
|
|
97
|
+
QueueProvider,
|
|
98
|
+
createQueueProvider,
|
|
99
|
+
QUEUE_PROVIDERS,
|
|
100
|
+
} = require('./queues');
|
|
96
101
|
|
|
97
102
|
module.exports = {
|
|
98
103
|
// assertions
|
|
@@ -187,6 +192,9 @@ module.exports = {
|
|
|
187
192
|
ModuleFactory,
|
|
188
193
|
// queues
|
|
189
194
|
QueuerUtil,
|
|
195
|
+
QueueProvider,
|
|
196
|
+
createQueueProvider,
|
|
197
|
+
QUEUE_PROVIDERS,
|
|
190
198
|
|
|
191
199
|
// utils
|
|
192
200
|
...utils,
|
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
* Follows hexagonal architecture with interface + adapters pattern.
|
|
6
6
|
*
|
|
7
7
|
* Providers:
|
|
8
|
-
* - eventbridge: AWS EventBridge Scheduler (production)
|
|
8
|
+
* - eventbridge: AWS EventBridge Scheduler (production on AWS)
|
|
9
|
+
* - netlify: Netlify poll-and-dispatch scheduler (production on Netlify)
|
|
9
10
|
* - mock: In-memory mock scheduler (local development)
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
const { SchedulerServiceInterface } = require('./scheduler-service-interface');
|
|
13
14
|
const { EventBridgeSchedulerAdapter } = require('./eventbridge-scheduler-adapter');
|
|
14
15
|
const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
|
|
16
|
+
const { NetlifySchedulerAdapter } = require('./netlify-scheduler-adapter');
|
|
15
17
|
const {
|
|
16
18
|
createSchedulerService,
|
|
17
19
|
SCHEDULER_PROVIDERS,
|
|
@@ -25,6 +27,7 @@ module.exports = {
|
|
|
25
27
|
// Adapters
|
|
26
28
|
EventBridgeSchedulerAdapter,
|
|
27
29
|
MockSchedulerAdapter,
|
|
30
|
+
NetlifySchedulerAdapter,
|
|
28
31
|
|
|
29
32
|
// Factory
|
|
30
33
|
createSchedulerService,
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
if (this.queueProvider) {
|
|
144
|
+
await this.queueProvider.send(
|
|
145
|
+
schedule.payload,
|
|
146
|
+
schedule.queueResourceId
|
|
147
|
+
);
|
|
148
|
+
} else {
|
|
149
|
+
console.log(
|
|
150
|
+
`[NetlifyScheduler] No queue provider — logging payload for: ${schedule.scheduleName}`,
|
|
151
|
+
JSON.stringify(schedule.payload)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
await this.repository.delete(schedule.scheduleName);
|
|
156
|
+
processed++;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(
|
|
159
|
+
`[NetlifyScheduler] Error processing schedule ${schedule.scheduleName}:`,
|
|
160
|
+
error
|
|
161
|
+
);
|
|
162
|
+
errors++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(
|
|
167
|
+
`[NetlifyScheduler] Processed ${processed} due schedules, ${errors} errors`
|
|
168
|
+
);
|
|
169
|
+
return { processed, errors };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = { NetlifySchedulerAdapter };
|
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
|
|
14
14
|
const { EventBridgeSchedulerAdapter } = require('./eventbridge-scheduler-adapter');
|
|
15
15
|
const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
|
|
16
|
+
const { NetlifySchedulerAdapter } = require('./netlify-scheduler-adapter');
|
|
16
17
|
|
|
17
18
|
const SCHEDULER_PROVIDERS = {
|
|
18
19
|
EVENTBRIDGE: 'eventbridge',
|
|
19
20
|
MOCK: 'mock',
|
|
21
|
+
NETLIFY: 'netlify',
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
const LOCAL_STAGES = ['dev', 'test', 'local'];
|
|
@@ -44,9 +46,11 @@ function determineProvider() {
|
|
|
44
46
|
* Create a scheduler service instance
|
|
45
47
|
*
|
|
46
48
|
* @param {Object} options
|
|
47
|
-
* @param {string} options.provider - Scheduler provider ('eventbridge' or '
|
|
49
|
+
* @param {string} options.provider - Scheduler provider ('eventbridge', 'mock', or 'netlify')
|
|
48
50
|
* @param {string} options.region - AWS region (for EventBridge)
|
|
49
51
|
* @param {boolean} options.verbose - Verbose logging (for Mock)
|
|
52
|
+
* @param {Object} options.repository - Schedule repository (for Netlify - persists schedules)
|
|
53
|
+
* @param {Object} options.queueProvider - Queue provider (for Netlify - dispatches due jobs)
|
|
50
54
|
* @returns {SchedulerServiceInterface} Implementation of scheduler interface
|
|
51
55
|
*/
|
|
52
56
|
function createSchedulerService(options = {}) {
|
|
@@ -61,6 +65,11 @@ function createSchedulerService(options = {}) {
|
|
|
61
65
|
return new MockSchedulerAdapter({
|
|
62
66
|
verbose: options.verbose,
|
|
63
67
|
});
|
|
68
|
+
case SCHEDULER_PROVIDERS.NETLIFY:
|
|
69
|
+
return new NetlifySchedulerAdapter({
|
|
70
|
+
repository: options.repository,
|
|
71
|
+
queueProvider: options.queueProvider,
|
|
72
|
+
});
|
|
64
73
|
default:
|
|
65
74
|
throw new Error(`Unknown scheduler provider: ${provider}`);
|
|
66
75
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.
|
|
4
|
+
"version": "2.0.0--canary.545.35013d9.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
43
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
44
|
-
"@friggframework/test": "2.0.0--canary.
|
|
42
|
+
"@friggframework/eslint-config": "2.0.0--canary.545.35013d9.0",
|
|
43
|
+
"@friggframework/prettier-config": "2.0.0--canary.545.35013d9.0",
|
|
44
|
+
"@friggframework/test": "2.0.0--canary.545.35013d9.0",
|
|
45
45
|
"@prisma/client": "^6.17.0",
|
|
46
46
|
"@types/lodash": "4.17.15",
|
|
47
47
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
"publishConfig": {
|
|
83
83
|
"access": "public"
|
|
84
84
|
},
|
|
85
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "35013d9802fee580e5df8e0ac792b9c683332d58"
|
|
86
86
|
}
|
|
@@ -429,3 +429,21 @@ model ScriptSchedule {
|
|
|
429
429
|
@@index([enabled])
|
|
430
430
|
@@map("ScriptSchedule")
|
|
431
431
|
}
|
|
432
|
+
|
|
433
|
+
/// One-time scheduled jobs for poll-and-dispatch scheduling pattern.
|
|
434
|
+
/// Used by providers without native one-time scheduling APIs (e.g., Netlify).
|
|
435
|
+
/// A cron function queries for due jobs and dispatches them to a queue.
|
|
436
|
+
model ScheduledJob {
|
|
437
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
438
|
+
scheduleName String @unique
|
|
439
|
+
scheduledAt DateTime
|
|
440
|
+
queueResourceId String
|
|
441
|
+
payload Json?
|
|
442
|
+
state String @default("PENDING")
|
|
443
|
+
|
|
444
|
+
createdAt DateTime @default(now())
|
|
445
|
+
updatedAt DateTime @updatedAt
|
|
446
|
+
|
|
447
|
+
@@index([state, scheduledAt])
|
|
448
|
+
@@map("ScheduledJob")
|
|
449
|
+
}
|
|
@@ -411,3 +411,20 @@ model ScriptSchedule {
|
|
|
411
411
|
|
|
412
412
|
@@index([enabled])
|
|
413
413
|
}
|
|
414
|
+
|
|
415
|
+
/// One-time scheduled jobs for poll-and-dispatch scheduling pattern.
|
|
416
|
+
/// Used by providers without native one-time scheduling APIs (e.g., Netlify).
|
|
417
|
+
/// A cron function queries for due jobs and dispatches them to a queue.
|
|
418
|
+
model ScheduledJob {
|
|
419
|
+
id Int @id @default(autoincrement())
|
|
420
|
+
scheduleName String @unique
|
|
421
|
+
scheduledAt DateTime
|
|
422
|
+
queueResourceId String
|
|
423
|
+
payload Json?
|
|
424
|
+
state String @default("PENDING")
|
|
425
|
+
|
|
426
|
+
createdAt DateTime @default(now())
|
|
427
|
+
updatedAt DateTime @updatedAt
|
|
428
|
+
|
|
429
|
+
@@index([state, scheduledAt])
|
|
430
|
+
}
|
package/queues/index.js
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
const { QueuerUtil } = require('./queuer-util');
|
|
2
|
+
const { QueueProvider } = require('./queue-provider');
|
|
3
|
+
const {
|
|
4
|
+
createQueueProvider,
|
|
5
|
+
determineProvider,
|
|
6
|
+
QUEUE_PROVIDERS,
|
|
7
|
+
} = require('./queue-provider-factory');
|
|
8
|
+
const {
|
|
9
|
+
SqsQueueProvider,
|
|
10
|
+
NetlifyBackgroundProvider,
|
|
11
|
+
QStashQueueProvider,
|
|
12
|
+
} = require('./providers');
|
|
13
|
+
|
|
2
14
|
module.exports = {
|
|
3
15
|
QueuerUtil,
|
|
16
|
+
QueueProvider,
|
|
17
|
+
createQueueProvider,
|
|
18
|
+
determineProvider,
|
|
19
|
+
QUEUE_PROVIDERS,
|
|
20
|
+
SqsQueueProvider,
|
|
21
|
+
NetlifyBackgroundProvider,
|
|
22
|
+
QStashQueueProvider,
|
|
4
23
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const { SqsQueueProvider } = require('./sqs-queue-provider');
|
|
2
|
+
const { NetlifyBackgroundProvider } = require('./netlify-background-provider');
|
|
3
|
+
const { QStashQueueProvider } = require('./qstash-queue-provider');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
SqsQueueProvider,
|
|
7
|
+
NetlifyBackgroundProvider,
|
|
8
|
+
QStashQueueProvider,
|
|
9
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Netlify Background Functions Queue Provider (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* Uses Netlify Background Functions for async job processing.
|
|
5
|
+
* Background functions have a 15-minute execution limit and are triggered via HTTP POST.
|
|
6
|
+
* They return 202 immediately while processing continues.
|
|
7
|
+
*
|
|
8
|
+
* Queue ID format: The function name or URL path for the background function.
|
|
9
|
+
* Convention: function files named with '-background' suffix (e.g., 'worker-background.js')
|
|
10
|
+
*
|
|
11
|
+
* Limitations:
|
|
12
|
+
* - No built-in retry (must implement manually)
|
|
13
|
+
* - No dead-letter queue
|
|
14
|
+
* - No FIFO ordering guarantees
|
|
15
|
+
* - 15-minute max execution time
|
|
16
|
+
*/
|
|
17
|
+
const { QueueProvider } = require('../queue-provider');
|
|
18
|
+
|
|
19
|
+
class NetlifyBackgroundProvider extends QueueProvider {
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
super();
|
|
22
|
+
// Base URL for triggering background functions (e.g., https://mysite.netlify.app)
|
|
23
|
+
this.baseUrl = options.baseUrl || process.env.URL || '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Send a message by invoking a Netlify Background Function via HTTP POST.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} message - Message payload
|
|
30
|
+
* @param {string} queueId - Background function path (e.g., '/.netlify/functions/worker-background')
|
|
31
|
+
*/
|
|
32
|
+
async send(message, queueId) {
|
|
33
|
+
const url = `${this.baseUrl}${queueId}`;
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
body: JSON.stringify(message),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (!response.ok && response.status !== 202) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Failed to invoke background function at ${queueId}: ${response.status} ${response.statusText}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { status: response.status, queueId };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Send batch of messages sequentially (background functions are HTTP-triggered).
|
|
51
|
+
*/
|
|
52
|
+
async batchSend(entries = [], queueId) {
|
|
53
|
+
const results = [];
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const result = await this.send(entry, queueId);
|
|
56
|
+
results.push(result);
|
|
57
|
+
}
|
|
58
|
+
return { results };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse Netlify Background Function event.
|
|
63
|
+
* Background functions receive the raw HTTP event body as the event.
|
|
64
|
+
*/
|
|
65
|
+
parseEvent(event) {
|
|
66
|
+
const body =
|
|
67
|
+
typeof event.body === 'string'
|
|
68
|
+
? JSON.parse(event.body)
|
|
69
|
+
: event.body;
|
|
70
|
+
|
|
71
|
+
// Background functions receive a single message per invocation
|
|
72
|
+
return [body];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { NetlifyBackgroundProvider };
|