@adobe/spacecat-shared-data-access 3.71.2 → 3.72.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 CHANGED
@@ -1,3 +1,19 @@
1
+ ## [@adobe/spacecat-shared-data-access-v3.72.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.72.0...@adobe/spacecat-shared-data-access-v3.72.1) (2026-05-28)
2
+
3
+ ### Bug Fixes
4
+
5
+ * (data-access) include all fixes in getAllFixesWithSuggestionsByOpportunityId (SITES-45307) ([#1634](https://github.com/adobe/spacecat-shared/issues/1634)) ([49e073e](https://github.com/adobe/spacecat-shared/commit/49e073e9e79813597fb11a922fc9f561136ce1d8))
6
+
7
+ ## [@adobe/spacecat-shared-data-access-v3.72.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.71.2...@adobe/spacecat-shared-data-access-v3.72.0) (2026-05-27)
8
+
9
+ ### Features
10
+
11
+ * **data-access:** add Preflight entity with AsyncJob relationship (SITES-45417) ([2b57e6a](https://github.com/adobe/spacecat-shared/commit/2b57e6a1af8de07cee588409faab0ec4827a5a7d))
12
+
13
+ ### Bug Fixes
14
+
15
+ * **data-access:** mint UUID v7 ids to match DB schema default ([#1633](https://github.com/adobe/spacecat-shared/issues/1633)) ([5c8f6fc](https://github.com/adobe/spacecat-shared/commit/5c8f6fcd454975703b4f670c0f560f43a768736a))
16
+
1
17
  ## [@adobe/spacecat-shared-data-access-v3.71.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.71.1...@adobe/spacecat-shared-data-access-v3.71.2) (2026-05-26)
2
18
 
3
19
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "3.71.2",
3
+ "version": "3.72.1",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -43,6 +43,7 @@ import SiteTopPageCollection from '../site-top-page/site-top-page.collection.js'
43
43
  import SuggestionCollection from '../suggestion/suggestion.collection.js';
44
44
  import SuggestionGrantCollection from '../suggestion-grant/suggestion-grant.collection.js';
45
45
  import PageIntentCollection from '../page-intent/page-intent.collection.js';
46
+ import PreflightCollection from '../preflight/preflight.collection.js';
46
47
  import ReportCollection from '../report/report.collection.js';
47
48
  import TrialUserCollection from '../trial-user/trial-user.collection.js';
48
49
  import TrialUserActivityCollection from '../trial-user-activity/trial-user-activity.collection.js';
@@ -83,6 +84,7 @@ import SiteTopPageSchema from '../site-top-page/site-top-page.schema.js';
83
84
  import SuggestionSchema from '../suggestion/suggestion.schema.js';
84
85
  import SuggestionGrantSchema from '../suggestion-grant/suggestion-grant.schema.js';
85
86
  import PageIntentSchema from '../page-intent/page-intent.schema.js';
87
+ import PreflightSchema from '../preflight/preflight.schema.js';
86
88
  import ReportSchema from '../report/report.schema.js';
87
89
  import TrialUserSchema from '../trial-user/trial-user.schema.js';
88
90
  import TrialUserActivitySchema from '../trial-user-activity/trial-user-activity.schema.js';
@@ -218,6 +220,7 @@ EntityRegistry.registerEntity(SiteTopPageSchema, SiteTopPageCollection);
218
220
  EntityRegistry.registerEntity(SuggestionSchema, SuggestionCollection);
219
221
  EntityRegistry.registerEntity(SuggestionGrantSchema, SuggestionGrantCollection);
220
222
  EntityRegistry.registerEntity(PageIntentSchema, PageIntentCollection);
223
+ EntityRegistry.registerEntity(PreflightSchema, PreflightCollection);
221
224
  EntityRegistry.registerEntity(ReportSchema, ReportCollection);
222
225
  EntityRegistry.registerEntity(TrialUserSchema, TrialUserCollection);
223
226
  EntityRegistry.registerEntity(TrialUserActivitySchema, TrialUserActivityCollection);
@@ -15,6 +15,7 @@ import {
15
15
  } from '@adobe/spacecat-shared-utils';
16
16
 
17
17
  import { SchemaBuilderError } from '../../errors/index.js';
18
+ import { uuidv7 } from '../../util/uuid.js';
18
19
  import {
19
20
  decapitalize,
20
21
  entityNameToAllPKValue,
@@ -29,17 +30,15 @@ import Schema from './schema.js';
29
30
 
30
31
  const DEFAULT_SERVICE_NAME = 'SpaceCat';
31
32
 
32
- /**
33
- * ID attribute configuration object.
34
- * Ensures a UUID-based "primary key".
35
- * @type {object}
36
- */
33
+ // The DB column default is `uuid_generate_v7()` (mysticat-data-service migration
34
+ // 20250130000001_initial_setup.sql). We mint v7 client-side so the in-memory
35
+ // record matches what the DB would have minted — sortable, locality-friendly.
37
36
  const ID_ATTRIBUTE_DATA = {
38
37
  type: 'string',
39
38
  postgrestField: 'id',
40
39
  required: true,
41
40
  readOnly: true,
42
- default: () => crypto.randomUUID(),
41
+ default: () => uuidv7(),
43
42
  validate: (value) => isValidUUID(value),
44
43
  };
45
44
 
@@ -193,15 +193,15 @@ class FixEntityCollection extends BaseCollection {
193
193
 
194
194
  /**
195
195
  * Gets all fixes with their suggestions for a specific opportunity.
196
- * Fetches all junction records for the opportunity, then batch-loads
197
- * fix entities and suggestions. Actual PostgREST call count is
198
- * 1 + ceil(N/50) + ceil(M/50) due to batchGetByKeys chunking.
196
+ * Fetches fix entities and mapping records in parallel, then batch-loads
197
+ * suggestions. Fixes without mapping records are included with
198
+ * suggestions: [].
199
199
  *
200
200
  * @async
201
201
  * @param {string} opportunityId - The ID of the opportunity.
202
202
  * @returns {Promise<Array>} - A promise that resolves to an array of objects containing:
203
203
  * - fixEntity: The FixEntity model
204
- * - suggestions: Array of associated Suggestion models
204
+ * - suggestions: Array of associated Suggestion models (empty if none mapped)
205
205
  * @throws {DataAccessError} - Throws an error if the query fails.
206
206
  * @throws {ValidationError} - Throws an error if opportunityId is not provided.
207
207
  */
@@ -211,10 +211,12 @@ class FixEntityCollection extends BaseCollection {
211
211
  try {
212
212
  const fixEntitySuggestionCollection = this.entityRegistry.getCollection('FixEntitySuggestionCollection');
213
213
 
214
- const fixEntitySuggestions = await fixEntitySuggestionCollection
215
- .allByIndexKeys({ opportunityId });
214
+ const [allFixEntities, fixEntitySuggestions] = await Promise.all([
215
+ this.allByOpportunityId(opportunityId),
216
+ fixEntitySuggestionCollection.allByIndexKeys({ opportunityId }),
217
+ ]);
216
218
 
217
- return this.#buildFixesWithSuggestions(fixEntitySuggestions);
219
+ return this.#buildAllFixesWithSuggestions(allFixEntities, fixEntitySuggestions);
218
220
  } catch (error) {
219
221
  if (error instanceof DataAccessError) {
220
222
  throw error;
@@ -276,6 +278,44 @@ class FixEntityCollection extends BaseCollection {
276
278
 
277
279
  return result;
278
280
  }
281
+
282
+ async #buildAllFixesWithSuggestions(allFixEntities, fixEntitySuggestions) {
283
+ if (allFixEntities.length === 0) {
284
+ return [];
285
+ }
286
+
287
+ const suggestionIdsByFixEntityId = {};
288
+ for (const junction of fixEntitySuggestions) {
289
+ const fixEntityId = junction.getFixEntityId();
290
+ const suggestionId = junction.getSuggestionId();
291
+ if (!suggestionIdsByFixEntityId[fixEntityId]) {
292
+ suggestionIdsByFixEntityId[fixEntityId] = [];
293
+ }
294
+ suggestionIdsByFixEntityId[fixEntityId].push(suggestionId);
295
+ }
296
+
297
+ const allSuggestionIds = Object.values(suggestionIdsByFixEntityId).flat();
298
+ const suggestionsById = {};
299
+
300
+ if (allSuggestionIds.length > 0) {
301
+ const suggestionCollection = this.entityRegistry.getCollection('SuggestionCollection');
302
+ const suggestions = await suggestionCollection.batchGetByKeys(
303
+ allSuggestionIds.map((id) => ({ [suggestionCollection.idName]: id })),
304
+ );
305
+ for (const suggestion of suggestions.data) {
306
+ suggestionsById[suggestion.getId()] = suggestion;
307
+ }
308
+ }
309
+
310
+ return allFixEntities.map((fixEntity) => {
311
+ const fixEntityId = fixEntity.getId();
312
+ const suggestionIds = suggestionIdsByFixEntityId[fixEntityId] || [];
313
+ const suggestions = suggestionIds
314
+ .map((id) => suggestionsById[id])
315
+ .filter(Boolean);
316
+ return { fixEntity, suggestions };
317
+ });
318
+ }
279
319
  }
280
320
 
281
321
  export default FixEntityCollection;
@@ -43,6 +43,7 @@ export * from './site/index.js';
43
43
  export * from './suggestion/index.js';
44
44
  export * from './suggestion-grant/index.js';
45
45
  export * from './page-intent/index.js';
46
+ export * from './preflight/index.js';
46
47
  export * from './report/index.js';
47
48
  export * from './trial-user/index.js';
48
49
  export * from './trial-user-activity/index.js';
@@ -0,0 +1,42 @@
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 { AsyncJob, BaseCollection, BaseModel, Site } from '../index.js';
14
+
15
+ export interface Preflight extends BaseModel {
16
+ getSiteId(): string;
17
+ getSite(): Promise<Site>;
18
+ getAsyncJobId(): string;
19
+ getAsyncJob(): Promise<AsyncJob>;
20
+ getUrl(): string;
21
+ getStatus(): string;
22
+ getCreatedBy(): { email: string; displayName?: string };
23
+ getStartedAt(): string | null;
24
+ getEndedAt(): string | null;
25
+ getResult(): object | null;
26
+ getError(): { code: string; message: string } | null;
27
+
28
+ setUrl(url: string): Preflight;
29
+ setStatus(status: 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'CANCELLED'): Preflight;
30
+ setCreatedBy(createdBy: { email: string; displayName?: string }): Preflight;
31
+ setStartedAt(startedAt: string): Preflight;
32
+ setEndedAt(endedAt: string): Preflight;
33
+ setResult(result: object): Preflight;
34
+ setError(error: { code: string; message: string }): Preflight;
35
+ }
36
+
37
+ export interface PreflightCollection extends BaseCollection<Preflight> {
38
+ allBySiteId(siteId: string): Promise<Preflight[]>;
39
+ findBySiteId(siteId: string): Promise<Preflight | null>;
40
+ findByAsyncJobId(asyncJobId: string): Promise<Preflight | null>;
41
+ allBySiteIdAndUrl(siteId: string, url?: string): Promise<Preflight[]>;
42
+ }
@@ -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 Preflight from './preflight.model.js';
14
+ import PreflightCollection from './preflight.collection.js';
15
+
16
+ export {
17
+ Preflight,
18
+ PreflightCollection,
19
+ };
@@ -0,0 +1,45 @@
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
+ * PreflightCollection - A collection class responsible for managing Preflight entities.
17
+ * Extends the BaseCollection to provide specific methods for interacting with Preflight records.
18
+ *
19
+ * allBySiteId(siteId) is auto-generated from the BELONGS_TO Site reference in the schema.
20
+ *
21
+ * @class PreflightCollection
22
+ * @extends BaseCollection
23
+ */
24
+ class PreflightCollection extends BaseCollection {
25
+ static COLLECTION_NAME = 'PreflightCollection';
26
+
27
+ /**
28
+ * Returns all preflights for a site, optionally filtered by URL.
29
+ * URL filtering is applied in-memory; volume is bounded by AsyncJob lifecycle
30
+ * (preflights cascade-delete when their backing AsyncJob is removed).
31
+ *
32
+ * @param {string} siteId - The site ID to query.
33
+ * @param {string} [url] - Optional URL to filter by.
34
+ * @returns {Promise<Preflight[]>}
35
+ */
36
+ async allBySiteIdAndUrl(siteId, url) {
37
+ const all = await this.allBySiteId(siteId);
38
+ if (!url) {
39
+ return all;
40
+ }
41
+ return all.filter((p) => p.getUrl() === url);
42
+ }
43
+ }
44
+
45
+ export default PreflightCollection;
@@ -0,0 +1,40 @@
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
+ * Preflight - A class representing a Preflight entity.
17
+ * Provides methods to access and manipulate Preflight-specific data.
18
+ *
19
+ * @class Preflight
20
+ * @extends BaseModel
21
+ */
22
+ class Preflight extends BaseModel {
23
+ static ENTITY_NAME = 'Preflight';
24
+
25
+ static Status = {
26
+ IN_PROGRESS: 'IN_PROGRESS',
27
+ COMPLETED: 'COMPLETED',
28
+ FAILED: 'FAILED',
29
+ CANCELLED: 'CANCELLED',
30
+ };
31
+
32
+ toJSON() {
33
+ const json = super.toJSON();
34
+ // asyncJobId is an internal FK reference — never exposed to API consumers
35
+ delete json.asyncJobId;
36
+ return json;
37
+ }
38
+ }
39
+
40
+ export default Preflight;
@@ -0,0 +1,55 @@
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 {
14
+ isObject, isValidUrl, isIsoDate,
15
+ } from '@adobe/spacecat-shared-utils';
16
+ import SchemaBuilder from '../base/schema.builder.js';
17
+ import Preflight from './preflight.model.js';
18
+ import PreflightCollection from './preflight.collection.js';
19
+
20
+ const schema = new SchemaBuilder(Preflight, PreflightCollection)
21
+ .addReference('belongs_to', 'Site', [], { required: true })
22
+ .addReference('belongs_to', 'AsyncJob', [], { required: true })
23
+ .addAttribute('url', {
24
+ type: 'string',
25
+ required: true,
26
+ validate: (value) => isValidUrl(value) && value.length <= 2048,
27
+ })
28
+ .addAttribute('status', {
29
+ type: Object.values(Preflight.Status),
30
+ required: true,
31
+ default: Preflight.Status.IN_PROGRESS,
32
+ })
33
+ .addAttribute('createdBy', {
34
+ type: 'map',
35
+ required: true,
36
+ validate: (value) => isObject(value) && typeof value.email === 'string' && value.email.length > 0,
37
+ })
38
+ .addAttribute('startedAt', {
39
+ type: 'string',
40
+ validate: (value) => !value || isIsoDate(value),
41
+ })
42
+ .addAttribute('endedAt', {
43
+ type: 'string',
44
+ validate: (value) => !value || isIsoDate(value),
45
+ })
46
+ .addAttribute('result', {
47
+ type: 'any',
48
+ validate: (value) => !value || isObject(value),
49
+ })
50
+ .addAttribute('error', {
51
+ type: 'map',
52
+ validate: (value) => !value || (isObject(value) && typeof value.code === 'string' && value.code.length > 0 && typeof value.message === 'string' && value.message.length > 0),
53
+ });
54
+
55
+ export default schema.build();
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Copyright 2026 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 { randomBytes } from 'crypto';
14
+
15
+ // Mirrors the database's `uuid_generate_v7()` plpgsql function defined in
16
+ // mysticat-data-service migration 20250130000001_initial_setup.sql so client-
17
+ // minted ids share the same time-sortable layout as DB-defaulted rows. The
18
+ // schema has been v7-by-design since Jan 2025; this keeps the ORM aligned.
19
+ /* eslint-disable no-bitwise */
20
+ function uuidv7() {
21
+ // Every byte is overwritten below (0-5 from the timestamp, 6-15 from
22
+ // randomBytes), so allocUnsafe avoids the zero-fill that alloc would do.
23
+ const bytes = Buffer.allocUnsafe(16);
24
+ const ms = BigInt(Date.now());
25
+
26
+ bytes[0] = Number((ms >> 40n) & 0xffn);
27
+ bytes[1] = Number((ms >> 32n) & 0xffn);
28
+ bytes[2] = Number((ms >> 24n) & 0xffn);
29
+ bytes[3] = Number((ms >> 16n) & 0xffn);
30
+ bytes[4] = Number((ms >> 8n) & 0xffn);
31
+ bytes[5] = Number(ms & 0xffn);
32
+
33
+ randomBytes(10).copy(bytes, 6);
34
+
35
+ bytes[6] = (bytes[6] & 0x0f) | 0x70;
36
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
37
+
38
+ const hex = bytes.toString('hex');
39
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
40
+ }
41
+ /* eslint-enable no-bitwise */
42
+
43
+ export { uuidv7 };