@seeka-labs/cli-apps 2.0.7-rc.2 → 2.0.12-alpha.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.
@@ -1,67 +1,67 @@
1
- import winston from 'winston';
2
-
3
- import { app, InvocationContext } from '@azure/functions';
4
-
5
- import { BackgroundJobRequestContext, deserialiseQueuePayload, queueNames, sendQueueMessageToPoisonQueue } from '../lib/jobs';
6
- import { backgroundJobLogger } from '../lib/logging';
7
- import { startServices } from '../lib/services';
8
- import { groupBy } from 'lodash';
9
- import { SeekaAppInstallState, tryGetInstallation } from '../lib/state/seeka/installations';
10
- import { SeekaActivityAcceptedWebhookContent } from '@seeka-labs/sdk-apps-server';
11
-
12
- app.storageQueue('queueExample', {
13
- queueName: queueNames.queueItemExampleQueueName,
14
- connection: 'AzureWebJobsStorage',
15
- handler: queueExample
16
- });
17
-
18
- export interface MyQueueItem extends BackgroundJobRequestContext {
19
- items: SeekaActivityAcceptedWebhookContent[];
20
- }
21
-
22
- export async function queueExample(queueItem: any, context: InvocationContext): Promise<void> {
23
- const logger = backgroundJobLogger(queueNames.queueItemExampleQueueName, undefined, context);
24
- logger.profile(`queue.${queueNames.queueItemExampleQueueName}`)
25
- try {
26
- // queueItem can either be a single item or an array of items
27
- const payload = deserialiseQueuePayload<MyQueueItem>(queueItem, logger);
28
-
29
- // Group by applicationInstallId
30
- const grouped = groupBy(payload, e => e.applicationInstallId);
31
-
32
- logger.verbose('Received queue batch to handle queue message', { batchSize: payload.length });
33
-
34
- // Process each group
35
- await startServices(logger);
36
- for (const [applicationInstallId, items] of Object.entries(grouped)) {
37
- if (items.length === 0) {
38
- logger.warn('No items to process for applicationInstallId', { applicationInstallId });
39
- continue;
40
- }
41
- const thisLogger = backgroundJobLogger(queueNames.queueItemExampleQueueName, items[0], context);
42
- try {
43
- const installation = await tryGetInstallation(applicationInstallId, true, thisLogger) as SeekaAppInstallState;
44
-
45
- // Execute sync
46
- // const batchItems = items.flatMap(e => e.items || []).filter(Boolean)
47
- // await executeLongRunningTask(batchItems, logger);
48
- }
49
- catch (err) {
50
- thisLogger.error('Error handling queue item to handle queue message', { ex: winston.exceptions.getAllInfo(err) })
51
- await sendQueueMessageToPoisonQueue(queueNames.queueItemExampleQueueName, {
52
- ...items[0],
53
- causationId: items[0].causationId,
54
- correlationId: context.invocationId,
55
- rows: items.flatMap(e => e.items || []).filter(Boolean)
56
- } as MyQueueItem, thisLogger);
57
- }
58
- }
59
- }
60
- catch (err) {
61
- logger.error('Error handling queue item to handle queue message', { ex: winston.exceptions.getAllInfo(err) })
62
- throw err; // Will retry based on host.json > extensions.queues.maxDequeueCount and then push to poison queue
63
- }
64
- finally {
65
- logger.profile(`queue.${queueNames.queueItemExampleQueueName}`)
66
- }
1
+ import winston from 'winston';
2
+
3
+ import { app, InvocationContext } from '@azure/functions';
4
+
5
+ import { BackgroundJobRequestContext, deserialiseQueuePayload, queueNames, sendQueueMessageToPoisonQueue } from '../lib/jobs';
6
+ import { backgroundJobLogger } from '../lib/logging';
7
+ import { startServices } from '../lib/services';
8
+ import { groupBy } from 'lodash';
9
+ import { SeekaAppInstallState, tryGetInstallation } from '../lib/state/seeka/installations';
10
+ import { SeekaActivityAcceptedWebhookContent } from '@seeka-labs/sdk-apps-server';
11
+
12
+ app.storageQueue('queueExample', {
13
+ queueName: queueNames.queueItemExampleQueueName,
14
+ connection: 'AzureWebJobsStorage',
15
+ handler: queueExample
16
+ });
17
+
18
+ export interface MyQueueItem extends BackgroundJobRequestContext {
19
+ items: SeekaActivityAcceptedWebhookContent[];
20
+ }
21
+
22
+ export async function queueExample(queueItem: any, context: InvocationContext): Promise<void> {
23
+ const logger = backgroundJobLogger(queueNames.queueItemExampleQueueName, undefined, context);
24
+ logger.profile(`queue.${queueNames.queueItemExampleQueueName}`)
25
+ try {
26
+ // queueItem can either be a single item or an array of items
27
+ const payload = deserialiseQueuePayload<MyQueueItem>(queueItem, logger);
28
+
29
+ // Group by applicationInstallId
30
+ const grouped = groupBy(payload, e => e.applicationInstallId);
31
+
32
+ logger.verbose('Received queue batch to handle queue message', { batchSize: payload.length });
33
+
34
+ // Process each group
35
+ await startServices(logger);
36
+ for (const [applicationInstallId, items] of Object.entries(grouped)) {
37
+ if (items.length === 0) {
38
+ logger.warn('No items to process for applicationInstallId', { applicationInstallId });
39
+ continue;
40
+ }
41
+ const thisLogger = backgroundJobLogger(queueNames.queueItemExampleQueueName, items[0], context);
42
+ try {
43
+ const installation = await tryGetInstallation(applicationInstallId, true, thisLogger) as SeekaAppInstallState;
44
+
45
+ // Execute sync
46
+ // const batchItems = items.flatMap(e => e.items || []).filter(Boolean)
47
+ // await executeLongRunningTask(batchItems, logger);
48
+ }
49
+ catch (err) {
50
+ thisLogger.error('Error handling queue item to handle queue message', { ex: winston.exceptions.getAllInfo(err) })
51
+ await sendQueueMessageToPoisonQueue(queueNames.queueItemExampleQueueName, {
52
+ ...items[0],
53
+ causationId: items[0].causationId,
54
+ correlationId: context.invocationId,
55
+ rows: items.flatMap(e => e.items || []).filter(Boolean)
56
+ } as MyQueueItem, thisLogger);
57
+ }
58
+ }
59
+ }
60
+ catch (err) {
61
+ logger.error('Error handling queue item to handle queue message', { ex: winston.exceptions.getAllInfo(err) })
62
+ throw err; // Will retry based on host.json > extensions.queues.maxDequeueCount and then push to poison queue
63
+ }
64
+ finally {
65
+ logger.profile(`queue.${queueNames.queueItemExampleQueueName}`)
66
+ }
67
67
  }
@@ -1,96 +1,96 @@
1
- import { QueueClient } from '@azure/storage-queue';
2
- import { isArray } from 'lodash';
3
- import type { Logger } from 'winston';
4
- import winston from 'winston';
5
-
6
- export interface BackgroundJobRequestContext {
7
- organisationId?: string;
8
- organisationBrandId?: string;
9
- applicationInstallId?: string;
10
- causationId: string
11
- correlationId: string
12
- }
13
-
14
- export const signatureHeaderName = 'x-signature-hmac';
15
- export const apiKeyHeaderName = 'x-api-key';
16
-
17
- export const jobNames = {
18
- pollingExample: 'polling-example',
19
- }
20
-
21
- export const queueNames = {
22
- queueItemExampleQueueName: 'sample-queue-name'
23
- }
24
-
25
- export const triggerBackgroundJob = async (queueName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
26
- const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, queueName);
27
- return triggerBackgroundJobWithQueue(queueClient, context, logger);
28
- }
29
-
30
- const serialiseQueuePayload = (payload: unknown): string => {
31
- const jsonString = JSON.stringify(payload)
32
- return Buffer.from(jsonString).toString('base64')
33
- }
34
-
35
- export const deserialiseQueuePayload = <TPayload>(queueItem: any, logger: Logger): TPayload[] => {
36
- try {
37
- if (typeof queueItem !== 'string') {
38
- if (isArray(queueItem)) {
39
- return queueItem
40
- }
41
- return [queueItem]
42
- }
43
- else {
44
- const jsonString = Buffer.from(queueItem, 'base64').toString()
45
- const parsed = JSON.parse(jsonString);
46
-
47
- if (isArray(parsed)) {
48
- return parsed
49
- }
50
- return [parsed]
51
- }
52
- }
53
- catch (err) {
54
- logger.error('Failed to deserialise queue payload', { ex: winston.exceptions.getAllInfo(err), queueItem });
55
- throw new Error(`Failed to deserialise queue payload`);
56
- }
57
- }
58
-
59
- export const triggerBackgroundJobWithQueue = async (queueClient: QueueClient, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
60
- const body = {
61
- ...context
62
- }
63
- const bodyStr = serialiseQueuePayload(body);
64
-
65
- const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
66
-
67
- if (response.errorCode) {
68
- const { requestId, date, errorCode } = response;
69
- logger.error("Failed to trigger background job", { body, requestId, date, errorCode })
70
- throw new Error(`Failed to trigger background job: ${response.errorCode}`);
71
- }
72
- else {
73
- logger.debug("Background job triggered", { body, messageId: response.messageId, context })
74
- }
75
- }
76
-
77
- export const sendQueueMessageToPoisonQueue = async (queueName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
78
- const body = {
79
- ...context
80
- }
81
- const bodyStr = serialiseQueuePayload(body);
82
-
83
- // https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cextensionv5&pivots=programming-language-typescript#poison-messages
84
- const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, `${queueName}-poison`);
85
-
86
- const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
87
-
88
- if (response.errorCode) {
89
- const { requestId, date, errorCode } = response;
90
- logger.error("Failed to push to poison queue", { body, requestId, date, errorCode })
91
- throw new Error(`Failed to push to poison queue: ${response.errorCode}`);
92
- }
93
- else {
94
- logger.verbose("Message pushed to poison queue", { body, messageId: response.messageId, context })
95
- }
1
+ import { QueueClient } from '@azure/storage-queue';
2
+ import { isArray } from 'lodash';
3
+ import type { Logger } from 'winston';
4
+ import winston from 'winston';
5
+
6
+ export interface BackgroundJobRequestContext {
7
+ organisationId?: string;
8
+ organisationBrandId?: string;
9
+ applicationInstallId?: string;
10
+ causationId: string
11
+ correlationId: string
12
+ }
13
+
14
+ export const signatureHeaderName = 'x-signature-hmac';
15
+ export const apiKeyHeaderName = 'x-api-key';
16
+
17
+ export const jobNames = {
18
+ pollingExample: 'polling-example',
19
+ }
20
+
21
+ export const queueNames = {
22
+ queueItemExampleQueueName: 'sample-queue-name'
23
+ }
24
+
25
+ export const triggerBackgroundJob = async (queueName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
26
+ const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, queueName);
27
+ return triggerBackgroundJobWithQueue(queueClient, context, logger);
28
+ }
29
+
30
+ const serialiseQueuePayload = (payload: unknown): string => {
31
+ const jsonString = JSON.stringify(payload)
32
+ return Buffer.from(jsonString).toString('base64')
33
+ }
34
+
35
+ export const deserialiseQueuePayload = <TPayload>(queueItem: any, logger: Logger): TPayload[] => {
36
+ try {
37
+ if (typeof queueItem !== 'string') {
38
+ if (isArray(queueItem)) {
39
+ return queueItem
40
+ }
41
+ return [queueItem]
42
+ }
43
+ else {
44
+ const jsonString = Buffer.from(queueItem, 'base64').toString()
45
+ const parsed = JSON.parse(jsonString);
46
+
47
+ if (isArray(parsed)) {
48
+ return parsed
49
+ }
50
+ return [parsed]
51
+ }
52
+ }
53
+ catch (err) {
54
+ logger.error('Failed to deserialise queue payload', { ex: winston.exceptions.getAllInfo(err), queueItem });
55
+ throw new Error(`Failed to deserialise queue payload`);
56
+ }
57
+ }
58
+
59
+ export const triggerBackgroundJobWithQueue = async (queueClient: QueueClient, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
60
+ const body = {
61
+ ...context
62
+ }
63
+ const bodyStr = serialiseQueuePayload(body);
64
+
65
+ const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
66
+
67
+ if (response.errorCode) {
68
+ const { requestId, date, errorCode } = response;
69
+ logger.error("Failed to trigger background job", { body, requestId, date, errorCode })
70
+ throw new Error(`Failed to trigger background job: ${response.errorCode}`);
71
+ }
72
+ else {
73
+ logger.debug("Background job triggered", { body, messageId: response.messageId, context })
74
+ }
75
+ }
76
+
77
+ export const sendQueueMessageToPoisonQueue = async (queueName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
78
+ const body = {
79
+ ...context
80
+ }
81
+ const bodyStr = serialiseQueuePayload(body);
82
+
83
+ // https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cextensionv5&pivots=programming-language-typescript#poison-messages
84
+ const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, `${queueName}-poison`);
85
+
86
+ const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
87
+
88
+ if (response.errorCode) {
89
+ const { requestId, date, errorCode } = response;
90
+ logger.error("Failed to push to poison queue", { body, requestId, date, errorCode })
91
+ throw new Error(`Failed to push to poison queue: ${response.errorCode}`);
92
+ }
93
+ else {
94
+ logger.verbose("Message pushed to poison queue", { body, messageId: response.messageId, context })
95
+ }
96
96
  }
@@ -1,41 +1,41 @@
1
- import winston, { Logger } from 'winston';
2
-
3
- import { connect, disconnect, isConnected } from '../state/redis';
4
-
5
- export const startServices = async (logger: Logger) => {
6
- logger.debug(`Trying to connect to Redis - ${process.env.REDIS_CONNECTION_HOST}`)
7
- try {
8
- if (isConnected()) {
9
- logger.verbose(`Redis already connected - ${process.env.REDIS_CONNECTION_HOST}`)
10
- }
11
- else {
12
- logger.profile('service.redis.connect')
13
- await connect();
14
- logger.profile('service.redis.connect')
15
- logger.debug(`Redis connected - ${process.env.REDIS_CONNECTION_HOST}`)
16
- }
17
- }
18
- catch (err) {
19
- logger.error(`Failed to connect to Redis - ${process.env.REDIS_CONNECTION_HOST}`, { ex: winston.exceptions.getAllInfo(err) })
20
- throw err;
21
- }
22
- }
23
-
24
- export const stopServices = async (logger: Logger) => {
25
- logger.debug(`Trying to disconnect from Redis - ${process.env.REDIS_CONNECTION_HOST}`)
26
- try {
27
- if (isConnected() === false) {
28
- logger.verbose(`Redis already disconnected - ${process.env.REDIS_CONNECTION_HOST}`)
29
- }
30
- else {
31
- logger.profile('service.redis.disconnect')
32
- await disconnect();
33
- logger.profile('service.redis.disconnect')
34
- logger.verbose(`Redis disconnected - ${process.env.REDIS_CONNECTION_HOST}`)
35
- }
36
- }
37
- catch (err) {
38
- logger.error(`Failed to disconnect from Redis - ${process.env.REDIS_CONNECTION_HOST}`, { ex: winston.exceptions.getAllInfo(err) })
39
- throw err;
40
- }
1
+ import winston, { Logger } from 'winston';
2
+
3
+ import { connect, disconnect, isConnected } from '../state/redis';
4
+
5
+ export const startServices = async (logger: Logger) => {
6
+ logger.debug(`Trying to connect to Redis - ${process.env.REDIS_CONNECTION_HOST}`)
7
+ try {
8
+ if (isConnected()) {
9
+ logger.verbose(`Redis already connected - ${process.env.REDIS_CONNECTION_HOST}`)
10
+ }
11
+ else {
12
+ logger.profile('service.redis.connect')
13
+ await connect();
14
+ logger.profile('service.redis.connect')
15
+ logger.debug(`Redis connected - ${process.env.REDIS_CONNECTION_HOST}`)
16
+ }
17
+ }
18
+ catch (err) {
19
+ logger.error(`Failed to connect to Redis - ${process.env.REDIS_CONNECTION_HOST}`, { ex: winston.exceptions.getAllInfo(err) })
20
+ throw err;
21
+ }
22
+ }
23
+
24
+ export const stopServices = async (logger: Logger) => {
25
+ logger.debug(`Trying to disconnect from Redis - ${process.env.REDIS_CONNECTION_HOST}`)
26
+ try {
27
+ if (isConnected() === false) {
28
+ logger.verbose(`Redis already disconnected - ${process.env.REDIS_CONNECTION_HOST}`)
29
+ }
30
+ else {
31
+ logger.profile('service.redis.disconnect')
32
+ await disconnect();
33
+ logger.profile('service.redis.disconnect')
34
+ logger.verbose(`Redis disconnected - ${process.env.REDIS_CONNECTION_HOST}`)
35
+ }
36
+ }
37
+ catch (err) {
38
+ logger.error(`Failed to disconnect from Redis - ${process.env.REDIS_CONNECTION_HOST}`, { ex: winston.exceptions.getAllInfo(err) })
39
+ throw err;
40
+ }
41
41
  }
@@ -1,18 +1,18 @@
1
- {
2
- "compilerOptions": {
3
- "allowJs": true,
4
- "skipLibCheck": true,
5
- "strict": false,
6
- "esModuleInterop": true,
7
- "outDir": "dist",
8
- "rootDir": ".",
9
- "sourceMap": true,
10
- "module": "CommonJS",
11
- "target": "ES6",
12
- "resolveJsonModule": true,
13
- "isolatedModules": false,
14
- },
15
- "include": [
16
- "src/functions/*.ts"
17
- ]
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "skipLibCheck": true,
5
+ "strict": false,
6
+ "esModuleInterop": true,
7
+ "outDir": "dist",
8
+ "rootDir": ".",
9
+ "sourceMap": true,
10
+ "module": "CommonJS",
11
+ "target": "ES6",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": false,
14
+ },
15
+ "include": [
16
+ "src/functions/*.ts"
17
+ ]
18
18
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seeka-app-example-name",
3
- "version": "2.0.7-rc.2",
3
+ "version": "2.0.12-alpha.0",
4
4
  "private": true,
5
5
  "workspaces": [
6
6
  "app/*"
@@ -1,25 +1,25 @@
1
- {
2
- "compilerOptions": {
3
- "allowJs": true,
4
- "skipLibCheck": true,
5
- "strict": false,
6
- "esModuleInterop": true,
7
- "sourceMap": true,
8
- "module": "CommonJS",
9
- "target": "ES6",
10
- "resolveJsonModule": true,
11
- "isolatedModules": false,
12
- "baseUrl": ".",
13
- "paths": {
14
- "@seeka-app-example-name/*": ["app/*/src"]
15
- }
16
- },
17
- "references": [
18
- { "path": "app/server-azure-function" },
19
- { "path": "app/browser" }
20
- ],
21
- "exclude": [
22
- "node_modules",
23
- "**/dist"
24
- ]
25
- }
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "skipLibCheck": true,
5
+ "strict": false,
6
+ "esModuleInterop": true,
7
+ "sourceMap": true,
8
+ "module": "CommonJS",
9
+ "target": "ES6",
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": false,
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@seeka-app-example-name/*": ["app/*/src"]
15
+ }
16
+ },
17
+ "references": [
18
+ { "path": "app/server-azure-function" },
19
+ { "path": "app/browser" }
20
+ ],
21
+ "exclude": [
22
+ "node_modules",
23
+ "**/dist"
24
+ ]
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seeka-labs/cli-apps",
3
- "version": "2.0.7-rc.2",
3
+ "version": "2.0.12-alpha.0",
4
4
  "description": "Seeka - Apps CLI",
5
5
  "author": "SEEKA <platform@seeka.co>",
6
6
  "license": "MIT",
@@ -40,11 +40,12 @@
40
40
  "commander": "^14.0.0",
41
41
  "cross-spawn": "^7.0.6",
42
42
  "esbuild": "^0.25.5",
43
+ "glob": "^11.0.3",
43
44
  "jest": "^29.7.0",
44
45
  "ts-jest": "^29.3.4",
45
46
  "typescript": "^5.8.3"
46
47
  },
47
- "gitHead": "3090cdc0329c28d427d15343ac3b2b486b5d5627",
48
+ "gitHead": "d56ee893384c9ebd99494844b5740de0f256789e",
48
49
  "dependencies": {
49
50
  "lodash-es": "^4.17.21",
50
51
  "source-map-support": "^0.5.21"
@@ -1,8 +0,0 @@
1
- nodeLinker: node-modules
2
-
3
- nmHoistingLimits: workspaces
4
-
5
- npmScopes:
6
- seeka-labs:
7
- npmRegistryServer: "https://npm.packages.seeka.services"
8
- npmAuthToken: "<<npm_registry_token>>"