@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
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QStash Queue Provider (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* Upstash QStash implementation - platform-agnostic HTTP-based message queue.
|
|
5
|
+
* Works on any platform (Netlify, Vercel, AWS, bare metal, etc.)
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Configurable retry with exponential backoff
|
|
9
|
+
* - Dead-letter queue via callbacks
|
|
10
|
+
* - At-least-once delivery
|
|
11
|
+
* - Scheduling support (delay, cron)
|
|
12
|
+
* - Message deduplication
|
|
13
|
+
*
|
|
14
|
+
* Requires:
|
|
15
|
+
* - QSTASH_TOKEN env var (from Upstash console)
|
|
16
|
+
* - QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY for verification
|
|
17
|
+
*
|
|
18
|
+
* Queue ID format: The destination URL that QStash will POST to when delivering the message.
|
|
19
|
+
*/
|
|
20
|
+
const { QueueProvider } = require('../queue-provider');
|
|
21
|
+
|
|
22
|
+
const QSTASH_API_BASE = 'https://qstash.upstash.io/v2';
|
|
23
|
+
|
|
24
|
+
class QStashQueueProvider extends QueueProvider {
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
super();
|
|
27
|
+
this.token =
|
|
28
|
+
options.token || process.env.QSTASH_TOKEN;
|
|
29
|
+
this.retries =
|
|
30
|
+
options.retries !== undefined ? options.retries : 3;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Send a message via QStash.
|
|
35
|
+
*
|
|
36
|
+
* @param {Object} message - Message payload
|
|
37
|
+
* @param {string} queueId - Destination URL that QStash will POST to
|
|
38
|
+
*/
|
|
39
|
+
async send(message, queueId) {
|
|
40
|
+
if (!this.token) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'QSTASH_TOKEN is required. Get one at https://console.upstash.com'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const headers = {
|
|
47
|
+
Authorization: `Bearer ${this.token}`,
|
|
48
|
+
'Content-Type': 'application/json',
|
|
49
|
+
'Upstash-Retries': String(this.retries),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const response = await fetch(
|
|
53
|
+
`${QSTASH_API_BASE}/publish/${queueId}`,
|
|
54
|
+
{
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers,
|
|
57
|
+
body: JSON.stringify(message),
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const errorBody = await response.text().catch(() => 'unknown');
|
|
63
|
+
throw new Error(
|
|
64
|
+
`QStash publish failed: ${response.status} ${response.statusText} - ${errorBody}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return response.json();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Send batch of messages via QStash batch API.
|
|
73
|
+
*/
|
|
74
|
+
async batchSend(entries = [], queueId) {
|
|
75
|
+
if (!this.token) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
'QSTASH_TOKEN is required. Get one at https://console.upstash.com'
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const messages = entries.map((entry) => ({
|
|
82
|
+
destination: queueId,
|
|
83
|
+
body: JSON.stringify(entry),
|
|
84
|
+
headers: { 'Content-Type': 'application/json' },
|
|
85
|
+
retries: this.retries,
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
const response = await fetch(`${QSTASH_API_BASE}/batch`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: {
|
|
91
|
+
Authorization: `Bearer ${this.token}`,
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(messages),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const errorBody = await response.text().catch(() => 'unknown');
|
|
99
|
+
throw new Error(
|
|
100
|
+
`QStash batch publish failed: ${response.status} - ${errorBody}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return response.json();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Parse QStash webhook delivery event.
|
|
109
|
+
* QStash POSTs the message body directly to the destination URL.
|
|
110
|
+
*/
|
|
111
|
+
parseEvent(event) {
|
|
112
|
+
const body =
|
|
113
|
+
typeof event.body === 'string'
|
|
114
|
+
? JSON.parse(event.body)
|
|
115
|
+
: event.body;
|
|
116
|
+
|
|
117
|
+
// QStash delivers a single message per invocation
|
|
118
|
+
return [body];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { QStashQueueProvider };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQS Queue Provider (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* AWS SQS implementation of the QueueProvider interface.
|
|
5
|
+
* Extracted from the original queuer-util.js to support multi-provider architecture.
|
|
6
|
+
*/
|
|
7
|
+
const { v4: uuid } = require('uuid');
|
|
8
|
+
const {
|
|
9
|
+
SQSClient,
|
|
10
|
+
SendMessageCommand,
|
|
11
|
+
SendMessageBatchCommand,
|
|
12
|
+
} = require('@aws-sdk/client-sqs');
|
|
13
|
+
const { QueueProvider } = require('../queue-provider');
|
|
14
|
+
|
|
15
|
+
const awsConfigOptions = () => {
|
|
16
|
+
const config = {};
|
|
17
|
+
if (process.env.IS_OFFLINE) {
|
|
18
|
+
config.credentials = {
|
|
19
|
+
accessKeyId: 'test-aws-key',
|
|
20
|
+
secretAccessKey: 'test-aws-secret',
|
|
21
|
+
};
|
|
22
|
+
config.region = 'us-east-1';
|
|
23
|
+
}
|
|
24
|
+
if (process.env.AWS_ENDPOINT) {
|
|
25
|
+
config.endpoint = process.env.AWS_ENDPOINT;
|
|
26
|
+
}
|
|
27
|
+
return config;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
class SqsQueueProvider extends QueueProvider {
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
this.sqs = new SQSClient(awsConfigOptions());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async send(message, queueUrl) {
|
|
37
|
+
const command = new SendMessageCommand({
|
|
38
|
+
MessageBody: JSON.stringify(message),
|
|
39
|
+
QueueUrl: queueUrl,
|
|
40
|
+
});
|
|
41
|
+
return this.sqs.send(command);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async batchSend(entries = [], queueUrl) {
|
|
45
|
+
const buffer = [];
|
|
46
|
+
const batchSize = 10;
|
|
47
|
+
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
buffer.push({
|
|
50
|
+
Id: uuid(),
|
|
51
|
+
MessageBody: JSON.stringify(entry),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (buffer.length === batchSize) {
|
|
55
|
+
const command = new SendMessageBatchCommand({
|
|
56
|
+
Entries: buffer,
|
|
57
|
+
QueueUrl: queueUrl,
|
|
58
|
+
});
|
|
59
|
+
await this.sqs.send(command);
|
|
60
|
+
buffer.splice(0, buffer.length);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (buffer.length > 0) {
|
|
65
|
+
const command = new SendMessageBatchCommand({
|
|
66
|
+
Entries: buffer,
|
|
67
|
+
QueueUrl: queueUrl,
|
|
68
|
+
});
|
|
69
|
+
return this.sqs.send(command);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse SQS event format: event.Records[].body (JSON string)
|
|
77
|
+
*/
|
|
78
|
+
parseEvent(event) {
|
|
79
|
+
const records = event?.Records || [];
|
|
80
|
+
return records.map((record) => JSON.parse(record.body));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { SqsQueueProvider };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue Provider Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates queue provider instances based on appDefinition or environment configuration.
|
|
5
|
+
*
|
|
6
|
+
* Priority order for determining provider:
|
|
7
|
+
* 1. Explicit `provider` option passed to factory
|
|
8
|
+
* 2. `appDefinition.queue.provider` property
|
|
9
|
+
* 3. `QUEUE_PROVIDER` environment variable
|
|
10
|
+
* 4. Default: 'sqs' (backward-compatible with existing AWS deployments)
|
|
11
|
+
*
|
|
12
|
+
* Supported providers:
|
|
13
|
+
* - 'sqs' → AWS SQS (default, existing behavior)
|
|
14
|
+
* - 'netlify-background' → Netlify Background Functions
|
|
15
|
+
* - 'qstash' → Upstash QStash (platform-agnostic)
|
|
16
|
+
*/
|
|
17
|
+
const { SqsQueueProvider } = require('./providers/sqs-queue-provider');
|
|
18
|
+
const {
|
|
19
|
+
NetlifyBackgroundProvider,
|
|
20
|
+
} = require('./providers/netlify-background-provider');
|
|
21
|
+
const { QStashQueueProvider } = require('./providers/qstash-queue-provider');
|
|
22
|
+
|
|
23
|
+
const QUEUE_PROVIDERS = {
|
|
24
|
+
SQS: 'sqs',
|
|
25
|
+
NETLIFY_BACKGROUND: 'netlify-background',
|
|
26
|
+
QSTASH: 'qstash',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Determine the queue provider based on config hierarchy
|
|
31
|
+
*
|
|
32
|
+
* @param {Object} [options]
|
|
33
|
+
* @param {string} [options.provider] - Explicit provider override
|
|
34
|
+
* @param {Object} [options.appDefinition] - Frigg app definition object
|
|
35
|
+
* @returns {string} Provider name
|
|
36
|
+
*/
|
|
37
|
+
function determineProvider(options = {}) {
|
|
38
|
+
if (options.provider) {
|
|
39
|
+
return options.provider;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (options.appDefinition?.queue?.provider) {
|
|
43
|
+
return options.appDefinition.queue.provider;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (process.env.QUEUE_PROVIDER) {
|
|
47
|
+
return process.env.QUEUE_PROVIDER;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return QUEUE_PROVIDERS.SQS;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create a queue provider instance
|
|
55
|
+
*
|
|
56
|
+
* @param {Object} [options]
|
|
57
|
+
* @param {string} [options.provider] - Queue provider name
|
|
58
|
+
* @param {Object} [options.appDefinition] - Frigg app definition (reads queue.provider)
|
|
59
|
+
* @param {Object} [options.providerOptions] - Options passed to the provider constructor
|
|
60
|
+
* @returns {QueueProvider} Implementation of queue provider interface
|
|
61
|
+
*/
|
|
62
|
+
function createQueueProvider(options = {}) {
|
|
63
|
+
const provider = determineProvider(options);
|
|
64
|
+
const providerOptions = options.providerOptions || {};
|
|
65
|
+
|
|
66
|
+
switch (provider) {
|
|
67
|
+
case QUEUE_PROVIDERS.SQS:
|
|
68
|
+
return new SqsQueueProvider(providerOptions);
|
|
69
|
+
case QUEUE_PROVIDERS.NETLIFY_BACKGROUND:
|
|
70
|
+
return new NetlifyBackgroundProvider(providerOptions);
|
|
71
|
+
case QUEUE_PROVIDERS.QSTASH:
|
|
72
|
+
return new QStashQueueProvider(providerOptions);
|
|
73
|
+
default:
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Unknown queue provider: '${provider}'. Supported: ${Object.values(QUEUE_PROVIDERS).join(', ')}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
createQueueProvider,
|
|
82
|
+
determineProvider,
|
|
83
|
+
QUEUE_PROVIDERS,
|
|
84
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue Provider Interface (Port)
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for queue message sending.
|
|
5
|
+
* All queue adapters must extend this interface.
|
|
6
|
+
*
|
|
7
|
+
* Following Frigg's hexagonal architecture pattern:
|
|
8
|
+
* - Port defines WHAT the service does (contract)
|
|
9
|
+
* - Adapters implement HOW (AWS SQS, Netlify Background Functions, QStash, etc.)
|
|
10
|
+
*/
|
|
11
|
+
class QueueProvider {
|
|
12
|
+
/**
|
|
13
|
+
* Send a single message to a queue
|
|
14
|
+
*
|
|
15
|
+
* @param {Object} message - Message payload (will be JSON-serialized)
|
|
16
|
+
* @param {string} queueId - Queue identifier (URL, name, or endpoint depending on provider)
|
|
17
|
+
* @returns {Promise<Object>} Provider-specific response
|
|
18
|
+
*/
|
|
19
|
+
async send(message, queueId) {
|
|
20
|
+
throw new Error('Method send must be implemented by subclass');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Send a batch of messages to a queue
|
|
25
|
+
*
|
|
26
|
+
* @param {Object[]} entries - Array of message payloads
|
|
27
|
+
* @param {string} queueId - Queue identifier
|
|
28
|
+
* @returns {Promise<Object>} Provider-specific response
|
|
29
|
+
*/
|
|
30
|
+
async batchSend(entries, queueId) {
|
|
31
|
+
throw new Error('Method batchSend must be implemented by subclass');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse an incoming event into an array of message payloads.
|
|
36
|
+
* Each provider has its own event shape (SQS Records, HTTP body, etc.)
|
|
37
|
+
*
|
|
38
|
+
* @param {Object} event - Raw event from the runtime (Lambda event, HTTP request, etc.)
|
|
39
|
+
* @returns {Object[]} Array of parsed message payloads
|
|
40
|
+
*/
|
|
41
|
+
parseEvent(event) {
|
|
42
|
+
throw new Error('Method parseEvent must be implemented by subclass');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { QueueProvider };
|