@adobe/spacecat-shared-data-access 2.108.0 → 3.0.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/CHANGELOG.md +21 -0
- package/README.md +260 -245
- package/docker-compose.test.yml +39 -0
- package/package.json +5 -6
- package/src/index.js +16 -5
- package/src/models/audit/audit.collection.js +6 -20
- package/src/models/base/base.collection.js +699 -395
- package/src/models/base/base.model.js +13 -6
- package/src/models/base/entity.registry.js +25 -5
- package/src/models/base/schema.builder.js +2 -0
- package/src/models/base/schema.js +15 -4
- package/src/models/consumer/consumer.collection.js +149 -0
- package/src/models/consumer/consumer.model.js +61 -0
- package/src/models/consumer/consumer.schema.js +65 -0
- package/src/models/consumer/index.d.ts +39 -0
- package/src/models/consumer/index.js +19 -0
- package/src/models/fix-entity-suggestion/fix-entity-suggestion.model.js +15 -0
- package/src/models/fix-entity-suggestion/fix-entity-suggestion.schema.js +12 -0
- package/src/models/index.d.ts +1 -0
- package/src/models/index.js +1 -0
- package/src/models/key-event/key-event.collection.js +23 -1
- package/src/models/latest-audit/latest-audit.collection.js +77 -3
- package/src/models/scrape-job/scrape-job.collection.js +38 -0
- package/src/models/scrape-job/scrape-job.schema.js +4 -2
- package/src/models/site/site.schema.js +1 -0
- package/src/models/site-enrollment/site-enrollment.collection.js +11 -1
- package/src/models/trial-user/trial-user.collection.js +0 -2
- package/src/models/trial-user/trial-user.schema.js +5 -1
- package/src/service/index.d.ts +7 -1
- package/src/service/index.js +33 -52
- package/src/util/index.js +2 -1
- package/src/util/logger-registry.js +12 -0
- package/src/util/patcher.js +64 -112
- package/src/util/postgrest.utils.js +203 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,203 @@
|
|
|
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 pluralize from 'pluralize';
|
|
14
|
+
|
|
15
|
+
const DEFAULT_PAGE_SIZE = 1000;
|
|
16
|
+
|
|
17
|
+
const ENTITY_TABLE_OVERRIDES = {
|
|
18
|
+
LatestAudit: 'audits',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const camelToSnake = (value) => value.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
|
|
22
|
+
|
|
23
|
+
const snakeToCamel = (value) => value.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
24
|
+
|
|
25
|
+
const entityToTableName = (entityName) => {
|
|
26
|
+
const override = ENTITY_TABLE_OVERRIDES[entityName];
|
|
27
|
+
if (override) {
|
|
28
|
+
return override;
|
|
29
|
+
}
|
|
30
|
+
return camelToSnake(pluralize.plural(entityName));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const encodeCursor = (offset) => Buffer.from(JSON.stringify({ offset }), 'utf-8').toString('base64');
|
|
34
|
+
|
|
35
|
+
const decodeCursor = (cursor) => {
|
|
36
|
+
if (!cursor) {
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const decoded = JSON.parse(Buffer.from(cursor, 'base64').toString('utf-8'));
|
|
42
|
+
return Number.isInteger(decoded.offset) && decoded.offset >= 0 ? decoded.offset : 0;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const createFieldMaps = (schema) => {
|
|
49
|
+
const toDbMap = {};
|
|
50
|
+
const toModelMap = {};
|
|
51
|
+
const attributes = schema.getAttributes();
|
|
52
|
+
const idName = typeof schema.getIdName === 'function' ? schema.getIdName() : undefined;
|
|
53
|
+
Object.keys(attributes).forEach((modelField) => {
|
|
54
|
+
const attribute = attributes[modelField] || {};
|
|
55
|
+
if (attribute.postgrestIgnore) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const dbField = attribute.postgrestField
|
|
59
|
+
|| (modelField === idName && modelField !== 'id' ? 'id' : camelToSnake(modelField));
|
|
60
|
+
toDbMap[modelField] = dbField;
|
|
61
|
+
toModelMap[dbField] = modelField;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const idAttribute = idName ? attributes[idName] : undefined;
|
|
65
|
+
if (idName
|
|
66
|
+
&& idName !== 'id'
|
|
67
|
+
&& idAttribute
|
|
68
|
+
&& !idAttribute.postgrestIgnore) {
|
|
69
|
+
toDbMap[idName] = 'id';
|
|
70
|
+
toModelMap.id = idName;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { toDbMap, toModelMap };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const toDbField = (field, map) => map[field] || camelToSnake(field);
|
|
77
|
+
|
|
78
|
+
const toModelField = (field, map) => map[field] || snakeToCamel(field);
|
|
79
|
+
|
|
80
|
+
const toDbRecord = (record, toDbMap, options = {}) => {
|
|
81
|
+
const { includeUnknown = false } = options;
|
|
82
|
+
return Object.entries(record).reduce((acc, [key, value]) => {
|
|
83
|
+
if (Object.prototype.hasOwnProperty.call(toDbMap, key)) {
|
|
84
|
+
acc[toDbMap[key]] = value;
|
|
85
|
+
return acc;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (includeUnknown) {
|
|
89
|
+
acc[toDbField(key, toDbMap)] = value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return acc;
|
|
93
|
+
}, {});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const looksLikeIsoDateTime = (value) => typeof value === 'string'
|
|
97
|
+
&& /^\d{4}-\d{2}-\d{2}T/.test(value)
|
|
98
|
+
&& /(?:Z|[+-]\d{2}:\d{2})$/.test(value);
|
|
99
|
+
|
|
100
|
+
const normalizeModelValue = (value) => {
|
|
101
|
+
if (value === null) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// PostgREST can return jsonb null scalars as [null] for some projections/aggregations.
|
|
106
|
+
// We normalize this artifact to "missing" so model payloads match v2 behavior.
|
|
107
|
+
if (Array.isArray(value) && value.length === 1 && value[0] === null) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (looksLikeIsoDateTime(value)) {
|
|
112
|
+
const date = new Date(value);
|
|
113
|
+
if (!Number.isNaN(date.getTime())) {
|
|
114
|
+
return date.toISOString();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return value;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const fromDbRecord = (record, toModelMap) => Object.entries(record).reduce((acc, [key, value]) => {
|
|
122
|
+
const normalized = normalizeModelValue(value);
|
|
123
|
+
if (normalized !== undefined) {
|
|
124
|
+
acc[toModelField(key, toModelMap)] = normalized;
|
|
125
|
+
}
|
|
126
|
+
return acc;
|
|
127
|
+
}, {});
|
|
128
|
+
|
|
129
|
+
const applyWhere = (query, whereFn, toDbMap) => {
|
|
130
|
+
if (typeof whereFn !== 'function') {
|
|
131
|
+
return query;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const attrs = new Proxy({}, {
|
|
135
|
+
get: (_, prop) => toDbField(String(prop), toDbMap),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const op = {
|
|
139
|
+
eq: (field, value) => ({ type: 'eq', field, value }),
|
|
140
|
+
ne: (field, value) => ({ type: 'ne', field, value }),
|
|
141
|
+
gt: (field, value) => ({ type: 'gt', field, value }),
|
|
142
|
+
gte: (field, value) => ({ type: 'gte', field, value }),
|
|
143
|
+
lt: (field, value) => ({ type: 'lt', field, value }),
|
|
144
|
+
lte: (field, value) => ({ type: 'lte', field, value }),
|
|
145
|
+
in: (field, value) => ({ type: 'in', field, value }),
|
|
146
|
+
is: (field, value) => ({ type: 'is', field, value }),
|
|
147
|
+
like: (field, value) => ({ type: 'like', field, value }),
|
|
148
|
+
ilike: (field, value) => ({ type: 'ilike', field, value }),
|
|
149
|
+
contains: (field, value) => ({ type: 'contains', field, value }),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const expression = whereFn(attrs, op);
|
|
153
|
+
if (!expression || typeof expression !== 'object') {
|
|
154
|
+
return query;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
switch (expression.type) {
|
|
158
|
+
case 'eq':
|
|
159
|
+
return query.eq(expression.field, expression.value);
|
|
160
|
+
case 'ne':
|
|
161
|
+
return query.neq(expression.field, expression.value);
|
|
162
|
+
case 'gt':
|
|
163
|
+
return query.gt(expression.field, expression.value);
|
|
164
|
+
case 'gte':
|
|
165
|
+
return query.gte(expression.field, expression.value);
|
|
166
|
+
case 'lt':
|
|
167
|
+
return query.lt(expression.field, expression.value);
|
|
168
|
+
case 'lte':
|
|
169
|
+
return query.lte(expression.field, expression.value);
|
|
170
|
+
case 'in':
|
|
171
|
+
return query.in(
|
|
172
|
+
expression.field,
|
|
173
|
+
Array.isArray(expression.value) ? expression.value : [expression.value],
|
|
174
|
+
);
|
|
175
|
+
case 'is':
|
|
176
|
+
return query.is(expression.field, expression.value);
|
|
177
|
+
case 'like':
|
|
178
|
+
return query.like(expression.field, expression.value);
|
|
179
|
+
case 'ilike':
|
|
180
|
+
return query.ilike(expression.field, expression.value);
|
|
181
|
+
case 'contains': {
|
|
182
|
+
const value = Array.isArray(expression.value) ? expression.value : [expression.value];
|
|
183
|
+
return query.contains(expression.field, value);
|
|
184
|
+
}
|
|
185
|
+
default:
|
|
186
|
+
throw new Error(`Unsupported where operator: ${expression.type}`);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export {
|
|
191
|
+
DEFAULT_PAGE_SIZE,
|
|
192
|
+
applyWhere,
|
|
193
|
+
camelToSnake,
|
|
194
|
+
createFieldMaps,
|
|
195
|
+
decodeCursor,
|
|
196
|
+
encodeCursor,
|
|
197
|
+
entityToTableName,
|
|
198
|
+
fromDbRecord,
|
|
199
|
+
snakeToCamel,
|
|
200
|
+
toDbField,
|
|
201
|
+
toDbRecord,
|
|
202
|
+
toModelField,
|
|
203
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["src/**/*.ts"],
|
|
23
|
+
"exclude": ["node_modules", "dist", "test"]
|
|
24
|
+
}
|