@adobe/spacecat-shared-data-access 2.16.1 → 2.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/package.json +3 -3
- package/src/models/async-job/README.md +67 -0
- package/src/models/async-job/async-job.collection.js +26 -0
- package/src/models/async-job/async-job.model.js +46 -0
- package/src/models/async-job/async-job.schema.js +65 -0
- package/src/models/async-job/index.d.ts +35 -0
- package/src/models/async-job/index.js +19 -0
- package/src/models/base/base.collection.js +1 -1
- package/src/models/base/entity.registry.js +3 -0
- package/src/models/index.d.ts +1 -0
- package/src/models/index.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-data-access-v2.17.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.17.0...@adobe/spacecat-shared-data-access-v2.17.1) (2025-05-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **deps:** update external fixes ([#712](https://github.com/adobe/spacecat-shared/issues/712)) ([76cebd2](https://github.com/adobe/spacecat-shared/commit/76cebd2a7a7b9799e4ca265833620eada01f5c8c))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-data-access-v2.17.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.16.1...@adobe/spacecat-shared-data-access-v2.17.0) (2025-04-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* async job ([#698](https://github.com/adobe/spacecat-shared/issues/698)) ([31cf58f](https://github.com/adobe/spacecat-shared/commit/31cf58fc9ef128d2cf094afe0286932c7edc369d))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-data-access-v2.16.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.16.0...@adobe/spacecat-shared-data-access-v2.16.1) (2025-04-26)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/spacecat-shared-data-access",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.17.1",
|
|
4
4
|
"description": "Shared modules of the Spacecat Services - Data Access",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@adobe/spacecat-shared-utils": "1.33.1",
|
|
38
|
-
"@aws-sdk/client-dynamodb": "3.
|
|
39
|
-
"@aws-sdk/lib-dynamodb": "3.
|
|
38
|
+
"@aws-sdk/client-dynamodb": "3.799.0",
|
|
39
|
+
"@aws-sdk/lib-dynamodb": "3.799.0",
|
|
40
40
|
"@types/joi": "17.2.3",
|
|
41
41
|
"aws-xray-sdk": "3.10.3",
|
|
42
42
|
"electrodb": "3.4.1",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# AsyncJob Entity for Asynchronous HTTP APIs
|
|
2
|
+
|
|
3
|
+
## Use Case
|
|
4
|
+
|
|
5
|
+
The `AsyncJob` entity is designed to support asynchronous HTTP APIs, such as those commonly implemented on AWS with API Gateway and Lambda. In this pattern:
|
|
6
|
+
|
|
7
|
+
- **Client submits a job** (e.g., a long-running computation or export) via an HTTP endpoint. The API returns HTTP 202 (Accepted) and a Job ID.
|
|
8
|
+
- **Client polls for job status** using the Job ID.
|
|
9
|
+
- **Client retrieves the result** when the job is completed, either inline or via a URL (e.g., S3).
|
|
10
|
+
|
|
11
|
+
This pattern is ideal for workflows where immediate results are not possible, and the client must check back later for completion.
|
|
12
|
+
|
|
13
|
+
## AsyncJob Schema Overview
|
|
14
|
+
|
|
15
|
+
The `AsyncJob` entity persists the state and metadata of each asynchronous job. Key attributes include:
|
|
16
|
+
|
|
17
|
+
- `asyncJobId`: Unique identifier (UUID v4) for the job.
|
|
18
|
+
- `status`: Job lifecycle status (`IN_PROGRESS`, `COMPLETED`, `FAILED`, `CANCELLED`).
|
|
19
|
+
- `createdAt`, `updatedAt`: Timestamps for auditing and sorting.
|
|
20
|
+
- `startedAt`, `endedAt`: Timestamps for when the job actually started and finished. Set automatically when job is created.
|
|
21
|
+
- `recordExpiresAt`: Unix epoch seconds for DynamoDB TTL/cleanup. Set automatically when job is created.
|
|
22
|
+
- `resultLocation`: URL or S3 URI where the result can be retrieved, or empty if not available.
|
|
23
|
+
- `resultType`: Optional. One of `S3`, `INLINE`, `URL`, or `null` if no result yet.
|
|
24
|
+
- `result`: Inline result data (if small enough), or `null`.
|
|
25
|
+
- `error`: Structured error object if the job failed.
|
|
26
|
+
- `metadata`: Arbitrary metadata (e.g., who submitted the job, job type, tags).
|
|
27
|
+
|
|
28
|
+
### Best Practices
|
|
29
|
+
- `resultType` is optional and should only be set when a result is available. For jobs in progress, leave it `null` or unset.
|
|
30
|
+
- Use `recordExpiresAt` to enable DynamoDB TTL for automatic cleanup of old jobs.
|
|
31
|
+
- Use `metadata` for extensibility (e.g., tracking submitter, job type, or tags).
|
|
32
|
+
|
|
33
|
+
## Usage Example
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
const { AsyncJob } = dataAccess;
|
|
37
|
+
|
|
38
|
+
// 1. Submit a new async job
|
|
39
|
+
const job = await AsyncJob.create({
|
|
40
|
+
status: 'IN_PROGRESS',
|
|
41
|
+
metadata: { submittedBy: 'user123', jobType: 'export', tags: ['export'] },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 2. Poll for job status
|
|
45
|
+
const polledJob = await AsyncJob.findById(job.getId());
|
|
46
|
+
if (polledJob.getStatus() === 'COMPLETED') {
|
|
47
|
+
// 3. Retrieve result
|
|
48
|
+
if (polledJob.getResultType() === 'S3') {
|
|
49
|
+
// Download from S3
|
|
50
|
+
const s3Url = polledJob.getResultLocation();
|
|
51
|
+
// ...
|
|
52
|
+
} else if (polledJob.getResultType() === 'INLINE') {
|
|
53
|
+
const result = polledJob.getResult();
|
|
54
|
+
// ...
|
|
55
|
+
}
|
|
56
|
+
} else if (polledJob.getStatus() === 'FAILED') {
|
|
57
|
+
const error = polledJob.getError();
|
|
58
|
+
// Handle error
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## When to Use AsyncJob
|
|
63
|
+
- For any API where work is performed asynchronously and clients must check back for results.
|
|
64
|
+
- For workflows where results may be large (S3), small (inline), or may fail and need error reporting.
|
|
65
|
+
- For jobs that should expire and be cleaned up automatically.
|
|
66
|
+
|
|
67
|
+
See the `AsyncJob` model, schema, and integration/unit tests for more details and usage patterns.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import BaseCollection from '../base/base.collection.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* AsyncJobCollection - A collection class responsible for managing AsyncJob entities.
|
|
17
|
+
* Extends the BaseCollection to provide specific methods for interacting with AsyncJob records.
|
|
18
|
+
*
|
|
19
|
+
* @class AsyncJobCollection
|
|
20
|
+
* @extends BaseCollection
|
|
21
|
+
*/
|
|
22
|
+
class AsyncJobCollection extends BaseCollection {
|
|
23
|
+
// Add custom methods or overrides here if needed
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default AsyncJobCollection;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import BaseModel from '../base/base.model.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* AsyncJob - A class representing an AsyncJob entity.
|
|
17
|
+
* Provides methods to access and manipulate AsyncJob-specific data.
|
|
18
|
+
*
|
|
19
|
+
* @class AsyncJob
|
|
20
|
+
* @extends BaseModel
|
|
21
|
+
*/
|
|
22
|
+
class AsyncJob extends BaseModel {
|
|
23
|
+
/**
|
|
24
|
+
* Async Job Status types.
|
|
25
|
+
* Any changes to this object needs to be reflected in the index.d.ts file as well.
|
|
26
|
+
*/
|
|
27
|
+
static Status = {
|
|
28
|
+
IN_PROGRESS: 'IN_PROGRESS',
|
|
29
|
+
COMPLETED: 'COMPLETED',
|
|
30
|
+
FAILED: 'FAILED',
|
|
31
|
+
CANCELLED: 'CANCELLED',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Async Job Result types.
|
|
36
|
+
*/
|
|
37
|
+
static ResultType = {
|
|
38
|
+
S3: 'S3',
|
|
39
|
+
INLINE: 'INLINE',
|
|
40
|
+
URL: 'URL',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Add custom methods or overrides here if needed
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default AsyncJob;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { isObject, isValidUrl, isIsoDate } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
import SchemaBuilder from '../base/schema.builder.js';
|
|
15
|
+
import AsyncJob from './async-job.model.js';
|
|
16
|
+
import AsyncJobCollection from './async-job.collection.js';
|
|
17
|
+
|
|
18
|
+
const schema = new SchemaBuilder(AsyncJob, AsyncJobCollection)
|
|
19
|
+
.withRecordExpiry(7)
|
|
20
|
+
.addAttribute('status', {
|
|
21
|
+
type: Object.values(AsyncJob.Status),
|
|
22
|
+
required: true,
|
|
23
|
+
})
|
|
24
|
+
.addAttribute('resultLocation', {
|
|
25
|
+
type: 'string',
|
|
26
|
+
validate: (value) => !value || isValidUrl(value) || value.startsWith('s3://'),
|
|
27
|
+
})
|
|
28
|
+
.addAttribute('resultType', {
|
|
29
|
+
type: 'string',
|
|
30
|
+
validate: (value) => !value || Object.values(AsyncJob.ResultType).includes(value),
|
|
31
|
+
})
|
|
32
|
+
.addAttribute('result', {
|
|
33
|
+
type: 'any',
|
|
34
|
+
validate: (value) => !value || isObject(value),
|
|
35
|
+
})
|
|
36
|
+
.addAttribute('error', {
|
|
37
|
+
type: 'map',
|
|
38
|
+
properties: {
|
|
39
|
+
code: { type: 'string' },
|
|
40
|
+
message: { type: 'string' },
|
|
41
|
+
details: { type: 'any' },
|
|
42
|
+
},
|
|
43
|
+
validate: (value) => !value || (isObject(value) && value.code && value.message),
|
|
44
|
+
})
|
|
45
|
+
.addAttribute('metadata', {
|
|
46
|
+
type: 'any',
|
|
47
|
+
validate: (value) => !value || isObject(value),
|
|
48
|
+
})
|
|
49
|
+
.addAttribute('startedAt', {
|
|
50
|
+
type: 'string',
|
|
51
|
+
required: true,
|
|
52
|
+
readOnly: true,
|
|
53
|
+
default: () => new Date().toISOString(),
|
|
54
|
+
validate: (value) => !value || isIsoDate(value),
|
|
55
|
+
})
|
|
56
|
+
.addAttribute('endedAt', {
|
|
57
|
+
type: 'string',
|
|
58
|
+
validate: (value) => !value || isIsoDate(value),
|
|
59
|
+
})
|
|
60
|
+
.addIndex(
|
|
61
|
+
{ composite: ['status'] },
|
|
62
|
+
{ composite: ['updatedAt'] },
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
export default schema.build();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { BaseCollection, BaseModel } from '../base';
|
|
14
|
+
|
|
15
|
+
export interface AsyncJob extends BaseModel {
|
|
16
|
+
getStatus(): string;
|
|
17
|
+
getResultLocation(): string;
|
|
18
|
+
getResultType(): string;
|
|
19
|
+
getResult(): object;
|
|
20
|
+
getError(): { code: string; message: string; details?: object } | null;
|
|
21
|
+
getMetadata(): object | null;
|
|
22
|
+
getRecordExpiressAt(): number;
|
|
23
|
+
setStatus(status: string): void;
|
|
24
|
+
setResultLocation(location: string): void;
|
|
25
|
+
setResultType(type: string): void;
|
|
26
|
+
setResult(result: object): void;
|
|
27
|
+
setError(error: { code: string; message: string; details?: object }): void;
|
|
28
|
+
setMetadata(metadata: object): void;
|
|
29
|
+
setExpiresAt(expiresAt: number): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AsyncJobCollection extends BaseCollection<AsyncJob> {
|
|
33
|
+
allByStatus(status: string): Promise<AsyncJob[]>;
|
|
34
|
+
findByStatus(status: string): Promise<AsyncJob | null>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import AsyncJob from './async-job.model.js';
|
|
14
|
+
import AsyncJobCollection from './async-job.collection.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
AsyncJob,
|
|
18
|
+
AsyncJobCollection,
|
|
19
|
+
};
|
|
@@ -422,7 +422,7 @@ class BaseCollection {
|
|
|
422
422
|
validatedItems.push({ ...removeElectroProperties(Item), ...item });
|
|
423
423
|
} catch (error) {
|
|
424
424
|
if (error instanceof ElectroValidationError) {
|
|
425
|
-
errorItems.push({ item, error: new ValidationError(error) });
|
|
425
|
+
errorItems.push({ item, error: new ValidationError('Validation error', this, error) });
|
|
426
426
|
}
|
|
427
427
|
}
|
|
428
428
|
});
|
|
@@ -14,6 +14,7 @@ import { DataAccessError } from '../../errors/index.js';
|
|
|
14
14
|
import { collectionNameToEntityName, decapitalize } from '../../util/util.js';
|
|
15
15
|
|
|
16
16
|
import ApiKeyCollection from '../api-key/api-key.collection.js';
|
|
17
|
+
import AsyncJobCollection from '../async-job/async-job.collection.js';
|
|
17
18
|
import AuditCollection from '../audit/audit.collection.js';
|
|
18
19
|
import ConfigurationCollection from '../configuration/configuration.collection.js';
|
|
19
20
|
import ExperimentCollection from '../experiment/experiment.collection.js';
|
|
@@ -30,6 +31,7 @@ import SiteTopPageCollection from '../site-top-page/site-top-page.collection.js'
|
|
|
30
31
|
import SuggestionCollection from '../suggestion/suggestion.collection.js';
|
|
31
32
|
|
|
32
33
|
import ApiKeySchema from '../api-key/api-key.schema.js';
|
|
34
|
+
import AsyncJobSchema from '../async-job/async-job.schema.js';
|
|
33
35
|
import AuditSchema from '../audit/audit.schema.js';
|
|
34
36
|
import ConfigurationSchema from '../configuration/configuration.schema.js';
|
|
35
37
|
import FixEntitySchema from '../fix-entity/fix-entity.schema.js';
|
|
@@ -127,6 +129,7 @@ class EntityRegistry {
|
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
EntityRegistry.registerEntity(ApiKeySchema, ApiKeyCollection);
|
|
132
|
+
EntityRegistry.registerEntity(AsyncJobSchema, AsyncJobCollection);
|
|
130
133
|
EntityRegistry.registerEntity(AuditSchema, AuditCollection);
|
|
131
134
|
EntityRegistry.registerEntity(ConfigurationSchema, ConfigurationCollection);
|
|
132
135
|
EntityRegistry.registerEntity(FixEntitySchema, FixEntityCollection);
|
package/src/models/index.d.ts
CHANGED