@superblocksteam/shared 0.9617.8 → 0.9618.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/dist/database-lifecycle/appDatabaseImport.d.ts +42 -7
- package/dist/database-lifecycle/appDatabaseImport.d.ts.map +1 -1
- package/dist/database-lifecycle/appDatabaseImport.js +66 -5
- package/dist/database-lifecycle/appDatabaseImport.js.map +1 -1
- package/dist/database-lifecycle/appDatabaseImport.test.js +79 -0
- package/dist/database-lifecycle/appDatabaseImport.test.js.map +1 -1
- package/dist/database-lifecycle/appDatabaseStatus.d.ts +18 -0
- package/dist/database-lifecycle/appDatabaseStatus.d.ts.map +1 -1
- package/dist/database-lifecycle/appDatabaseStatus.js +21 -0
- package/dist/database-lifecycle/appDatabaseStatus.js.map +1 -1
- package/dist/database-lifecycle/appDatabaseStatus.test.js +11 -0
- package/dist/database-lifecycle/appDatabaseStatus.test.js.map +1 -1
- package/dist/database-lifecycle/dataImport.d.ts +45 -2
- package/dist/database-lifecycle/dataImport.d.ts.map +1 -1
- package/dist/database-lifecycle/dataImport.js +209 -3
- package/dist/database-lifecycle/dataImport.js.map +1 -1
- package/dist/database-lifecycle/dataImport.test.js +60 -0
- package/dist/database-lifecycle/dataImport.test.js.map +1 -1
- package/dist/database-lifecycle/importCompatibility.d.ts +40 -0
- package/dist/database-lifecycle/importCompatibility.d.ts.map +1 -0
- package/dist/database-lifecycle/importCompatibility.js +121 -0
- package/dist/database-lifecycle/importCompatibility.js.map +1 -0
- package/dist/database-lifecycle/importCompatibility.test.d.ts +2 -0
- package/dist/database-lifecycle/importCompatibility.test.d.ts.map +1 -0
- package/dist/database-lifecycle/importCompatibility.test.js +114 -0
- package/dist/database-lifecycle/importCompatibility.test.js.map +1 -0
- package/dist/database-lifecycle/index.d.ts +29 -5
- package/dist/database-lifecycle/index.d.ts.map +1 -1
- package/dist/database-lifecycle/index.js +18 -5
- package/dist/database-lifecycle/index.js.map +1 -1
- package/dist/database-lifecycle/index.test.js +4 -1
- package/dist/database-lifecycle/index.test.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types/audit/ocsf.d.ts +2 -1
- package/dist/types/audit/ocsf.d.ts.map +1 -1
- package/dist/types/audit/ocsf.js +8 -0
- package/dist/types/audit/ocsf.js.map +1 -1
- package/dist/types/codeSecurity/index.d.ts +2 -1
- package/dist/types/codeSecurity/index.d.ts.map +1 -1
- package/dist/types/codeSecurity/index.js +43 -5
- package/dist/types/codeSecurity/index.js.map +1 -1
- package/dist/types/codeSecurity/index.test.js +37 -3
- package/dist/types/codeSecurity/index.test.js.map +1 -1
- package/dist/types/datasource/auth.d.ts +1 -0
- package/dist/types/datasource/auth.d.ts.map +1 -1
- package/dist/types/datasource/auth.js +8 -2
- package/dist/types/datasource/auth.js.map +1 -1
- package/dist/types/datasource/auth.test.js +19 -1
- package/dist/types/datasource/auth.test.js.map +1 -1
- package/dist/types/integration/index.d.ts +16 -1
- package/dist/types/integration/index.d.ts.map +1 -1
- package/dist/types/integration/index.js +8 -1
- package/dist/types/integration/index.js.map +1 -1
- package/dist/types/policyGate/index.d.ts +1 -0
- package/dist/types/policyGate/index.d.ts.map +1 -1
- package/dist/types/policyGate/index.js +3 -1
- package/dist/types/policyGate/index.js.map +1 -1
- package/dist/types/publishPipeline/index.d.ts +2 -0
- package/dist/types/publishPipeline/index.d.ts.map +1 -1
- package/dist/types/publishPipeline/index.js.map +1 -1
- package/dist/types/publishPipeline/plan.d.ts +1 -1
- package/dist/types/publishPipeline/plan.d.ts.map +1 -1
- package/dist/types/publishPipeline/plan.js.map +1 -1
- package/dist/types/rbac/index.d.ts +0 -5
- package/dist/types/rbac/index.d.ts.map +1 -1
- package/dist/types/rbac/index.js.map +1 -1
- package/dist/types/response/index.d.ts +0 -1
- package/dist/types/response/index.d.ts.map +1 -1
- package/dist/types/response/index.js +0 -1
- package/dist/types/response/index.js.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseImport.d.ts +42 -7
- package/dist-esm/database-lifecycle/appDatabaseImport.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseImport.js +65 -5
- package/dist-esm/database-lifecycle/appDatabaseImport.js.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseImport.test.js +79 -0
- package/dist-esm/database-lifecycle/appDatabaseImport.test.js.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseStatus.d.ts +18 -0
- package/dist-esm/database-lifecycle/appDatabaseStatus.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseStatus.js +18 -0
- package/dist-esm/database-lifecycle/appDatabaseStatus.js.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseStatus.test.js +12 -1
- package/dist-esm/database-lifecycle/appDatabaseStatus.test.js.map +1 -1
- package/dist-esm/database-lifecycle/dataImport.d.ts +45 -2
- package/dist-esm/database-lifecycle/dataImport.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/dataImport.js +205 -3
- package/dist-esm/database-lifecycle/dataImport.js.map +1 -1
- package/dist-esm/database-lifecycle/dataImport.test.js +61 -1
- package/dist-esm/database-lifecycle/dataImport.test.js.map +1 -1
- package/dist-esm/database-lifecycle/importCompatibility.d.ts +40 -0
- package/dist-esm/database-lifecycle/importCompatibility.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/importCompatibility.js +117 -0
- package/dist-esm/database-lifecycle/importCompatibility.js.map +1 -0
- package/dist-esm/database-lifecycle/importCompatibility.test.d.ts +2 -0
- package/dist-esm/database-lifecycle/importCompatibility.test.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/importCompatibility.test.js +112 -0
- package/dist-esm/database-lifecycle/importCompatibility.test.js.map +1 -0
- package/dist-esm/database-lifecycle/index.d.ts +29 -5
- package/dist-esm/database-lifecycle/index.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/index.js +13 -5
- package/dist-esm/database-lifecycle/index.js.map +1 -1
- package/dist-esm/database-lifecycle/index.test.js +4 -1
- package/dist-esm/database-lifecycle/index.test.js.map +1 -1
- package/dist-esm/index.d.ts +1 -0
- package/dist-esm/index.d.ts.map +1 -1
- package/dist-esm/index.js +1 -0
- package/dist-esm/index.js.map +1 -1
- package/dist-esm/types/audit/ocsf.d.ts +2 -1
- package/dist-esm/types/audit/ocsf.d.ts.map +1 -1
- package/dist-esm/types/audit/ocsf.js +8 -0
- package/dist-esm/types/audit/ocsf.js.map +1 -1
- package/dist-esm/types/codeSecurity/index.d.ts +2 -1
- package/dist-esm/types/codeSecurity/index.d.ts.map +1 -1
- package/dist-esm/types/codeSecurity/index.js +43 -5
- package/dist-esm/types/codeSecurity/index.js.map +1 -1
- package/dist-esm/types/codeSecurity/index.test.js +37 -3
- package/dist-esm/types/codeSecurity/index.test.js.map +1 -1
- package/dist-esm/types/datasource/auth.d.ts +1 -0
- package/dist-esm/types/datasource/auth.d.ts.map +1 -1
- package/dist-esm/types/datasource/auth.js +7 -1
- package/dist-esm/types/datasource/auth.js.map +1 -1
- package/dist-esm/types/datasource/auth.test.js +20 -2
- package/dist-esm/types/datasource/auth.test.js.map +1 -1
- package/dist-esm/types/integration/index.d.ts +16 -1
- package/dist-esm/types/integration/index.d.ts.map +1 -1
- package/dist-esm/types/integration/index.js +8 -1
- package/dist-esm/types/integration/index.js.map +1 -1
- package/dist-esm/types/policyGate/index.d.ts +1 -0
- package/dist-esm/types/policyGate/index.d.ts.map +1 -1
- package/dist-esm/types/policyGate/index.js +2 -0
- package/dist-esm/types/policyGate/index.js.map +1 -1
- package/dist-esm/types/publishPipeline/index.d.ts +2 -0
- package/dist-esm/types/publishPipeline/index.d.ts.map +1 -1
- package/dist-esm/types/publishPipeline/index.js.map +1 -1
- package/dist-esm/types/publishPipeline/plan.d.ts +1 -1
- package/dist-esm/types/publishPipeline/plan.d.ts.map +1 -1
- package/dist-esm/types/publishPipeline/plan.js.map +1 -1
- package/dist-esm/types/rbac/index.d.ts +0 -5
- package/dist-esm/types/rbac/index.d.ts.map +1 -1
- package/dist-esm/types/rbac/index.js.map +1 -1
- package/dist-esm/types/response/index.d.ts +0 -1
- package/dist-esm/types/response/index.d.ts.map +1 -1
- package/dist-esm/types/response/index.js +0 -1
- package/dist-esm/types/response/index.js.map +1 -1
- package/package.json +2 -2
- package/src/database-lifecycle/appDatabaseImport.test.ts +95 -0
- package/src/database-lifecycle/appDatabaseImport.ts +134 -8
- package/src/database-lifecycle/appDatabaseStatus.test.ts +20 -1
- package/src/database-lifecycle/appDatabaseStatus.ts +39 -0
- package/src/database-lifecycle/dataImport.test.ts +75 -0
- package/src/database-lifecycle/dataImport.ts +267 -4
- package/src/database-lifecycle/importCompatibility.test.ts +146 -0
- package/src/database-lifecycle/importCompatibility.ts +148 -0
- package/src/database-lifecycle/index.test.ts +4 -1
- package/src/database-lifecycle/index.ts +54 -4
- package/src/index.ts +1 -0
- package/src/types/audit/ocsf.ts +10 -1
- package/src/types/codeSecurity/index.test.ts +40 -3
- package/src/types/codeSecurity/index.ts +45 -4
- package/src/types/datasource/auth.test.ts +25 -1
- package/src/types/datasource/auth.ts +9 -1
- package/src/types/integration/index.ts +17 -0
- package/src/types/policyGate/index.ts +3 -0
- package/src/types/publishPipeline/index.ts +2 -0
- package/src/types/publishPipeline/plan.ts +1 -1
- package/src/types/rbac/index.ts +0 -8
- package/src/types/response/index.ts +0 -1
- package/dist/types/response/GetGroupPermissionEntitiesResponseBody.d.ts +0 -3
- package/dist/types/response/GetGroupPermissionEntitiesResponseBody.d.ts.map +0 -1
- package/dist/types/response/GetGroupPermissionEntitiesResponseBody.js +0 -3
- package/dist/types/response/GetGroupPermissionEntitiesResponseBody.js.map +0 -1
- package/dist-esm/types/response/GetGroupPermissionEntitiesResponseBody.d.ts +0 -3
- package/dist-esm/types/response/GetGroupPermissionEntitiesResponseBody.d.ts.map +0 -1
- package/dist-esm/types/response/GetGroupPermissionEntitiesResponseBody.js +0 -2
- package/dist-esm/types/response/GetGroupPermissionEntitiesResponseBody.js.map +0 -1
- package/src/types/response/GetGroupPermissionEntitiesResponseBody.ts +0 -3
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { DataImportKind, DataImportState } from './dataImport.js';
|
|
2
2
|
|
|
3
|
-
export const APP_DATABASE_IMPORT_OPERATIONS = [
|
|
3
|
+
export const APP_DATABASE_IMPORT_OPERATIONS = [
|
|
4
|
+
'prepare_import_prefix_upload',
|
|
5
|
+
'inspect_import_prefix',
|
|
6
|
+
'capture_integration_pg_dump',
|
|
7
|
+
'restore_integration_pg_dump',
|
|
8
|
+
'restore_import_data'
|
|
9
|
+
] as const;
|
|
4
10
|
// Subset of DATA_IMPORT_STATES. Prefix success is schema_validated, not ready.
|
|
5
11
|
export const APP_DATABASE_IMPORT_STATES = [
|
|
6
12
|
'requested',
|
|
@@ -8,7 +14,10 @@ export const APP_DATABASE_IMPORT_STATES = [
|
|
|
8
14
|
'awaiting_prefix_upload',
|
|
9
15
|
'validating_schema',
|
|
10
16
|
'schema_validated',
|
|
17
|
+
'capturing_source',
|
|
18
|
+
'restoring',
|
|
11
19
|
'uploading',
|
|
20
|
+
'upload_completed',
|
|
12
21
|
'importing',
|
|
13
22
|
'completed',
|
|
14
23
|
'failed',
|
|
@@ -26,6 +35,13 @@ export const APP_DATABASE_IMPORT_ERROR_CODES = [
|
|
|
26
35
|
'internal',
|
|
27
36
|
'invalid_input',
|
|
28
37
|
'size_mismatch',
|
|
38
|
+
'source_connection_failed',
|
|
39
|
+
'source_dump_failed',
|
|
40
|
+
'source_integration_forbidden',
|
|
41
|
+
'source_integration_not_found',
|
|
42
|
+
'source_integration_unsupported',
|
|
43
|
+
'source_profile_unavailable',
|
|
44
|
+
'source_worker_mismatch',
|
|
29
45
|
'checksum_mismatch',
|
|
30
46
|
'commit_uncertain',
|
|
31
47
|
'stream_failed',
|
|
@@ -45,11 +61,18 @@ export type AppDatabaseImportValidationMetadata = {
|
|
|
45
61
|
export type AppDatabaseImportDispatchDetails = {
|
|
46
62
|
importId: string;
|
|
47
63
|
kind: AppDatabaseImportKind;
|
|
48
|
-
prefixSizeBytes: number;
|
|
49
64
|
profileToken: string;
|
|
50
65
|
resourceToken: string;
|
|
51
66
|
};
|
|
52
67
|
|
|
68
|
+
export type AppDatabaseImportSource = {
|
|
69
|
+
integrationConfigurationId: string;
|
|
70
|
+
integrationId: string;
|
|
71
|
+
profile: string;
|
|
72
|
+
profileId: string;
|
|
73
|
+
type: 'integration';
|
|
74
|
+
};
|
|
75
|
+
|
|
53
76
|
type AppDatabaseImportDispatchBase<TDesiredSpec> = {
|
|
54
77
|
applicationId: string;
|
|
55
78
|
bindingId: string;
|
|
@@ -71,17 +94,35 @@ type AppDatabaseImportDispatchBase<TDesiredSpec> = {
|
|
|
71
94
|
export type PrepareImportPrefixUploadDispatchPayloadEnvelope<TDesiredSpec> = AppDatabaseImportDispatchBase<TDesiredSpec> & {
|
|
72
95
|
import: AppDatabaseImportDispatchDetails & {
|
|
73
96
|
expectedSizeBytes: number;
|
|
97
|
+
prefixSizeBytes: number;
|
|
74
98
|
};
|
|
75
99
|
operation: 'prepare_import_prefix_upload';
|
|
76
100
|
};
|
|
77
101
|
|
|
78
102
|
export type InspectImportPrefixDispatchPayloadEnvelope<TDesiredSpec> = AppDatabaseImportDispatchBase<TDesiredSpec> & {
|
|
79
103
|
import: AppDatabaseImportDispatchDetails & {
|
|
104
|
+
prefixSizeBytes: number;
|
|
80
105
|
prefixObjectKey: string;
|
|
81
106
|
};
|
|
82
107
|
operation: 'inspect_import_prefix';
|
|
83
108
|
};
|
|
84
109
|
|
|
110
|
+
export type CaptureIntegrationPgDumpDispatchPayloadEnvelope<TDesiredSpec> = AppDatabaseImportDispatchBase<TDesiredSpec> & {
|
|
111
|
+
import: AppDatabaseImportDispatchDetails & {
|
|
112
|
+
source: AppDatabaseImportSource;
|
|
113
|
+
};
|
|
114
|
+
operation: 'capture_integration_pg_dump';
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export type RestoreIntegrationPgDumpDispatchPayloadEnvelope<TDesiredSpec> = AppDatabaseImportDispatchBase<TDesiredSpec> & {
|
|
118
|
+
import: AppDatabaseImportDispatchDetails & {
|
|
119
|
+
objectKey: string;
|
|
120
|
+
sha256: string;
|
|
121
|
+
sizeBytes: number;
|
|
122
|
+
};
|
|
123
|
+
operation: 'restore_integration_pg_dump';
|
|
124
|
+
};
|
|
125
|
+
|
|
85
126
|
export type RestoreImportDataDispatchPayloadEnvelope<TDesiredSpec> = AppDatabaseImportDispatchBase<TDesiredSpec> & {
|
|
86
127
|
import: AppDatabaseImportDispatchDetails & {
|
|
87
128
|
objectKey: string;
|
|
@@ -91,9 +132,11 @@ export type RestoreImportDataDispatchPayloadEnvelope<TDesiredSpec> = AppDatabase
|
|
|
91
132
|
};
|
|
92
133
|
|
|
93
134
|
export type AppDatabaseImportDispatchPayloadEnvelope<TDesiredSpec> =
|
|
135
|
+
| CaptureIntegrationPgDumpDispatchPayloadEnvelope<TDesiredSpec>
|
|
94
136
|
| InspectImportPrefixDispatchPayloadEnvelope<TDesiredSpec>
|
|
95
137
|
| PrepareImportPrefixUploadDispatchPayloadEnvelope<TDesiredSpec>
|
|
96
|
-
| RestoreImportDataDispatchPayloadEnvelope<TDesiredSpec
|
|
138
|
+
| RestoreImportDataDispatchPayloadEnvelope<TDesiredSpec>
|
|
139
|
+
| RestoreIntegrationPgDumpDispatchPayloadEnvelope<TDesiredSpec>;
|
|
97
140
|
|
|
98
141
|
export type PrepareImportPrefixUploadCallbackResult = {
|
|
99
142
|
prefixObjectKey: string;
|
|
@@ -114,7 +157,25 @@ export type InspectImportPrefixCallbackResult =
|
|
|
114
157
|
validationMetadata: AppDatabaseImportValidationMetadata;
|
|
115
158
|
};
|
|
116
159
|
|
|
117
|
-
export type
|
|
160
|
+
export type CaptureIntegrationPgDumpCallbackResult = {
|
|
161
|
+
artifact: {
|
|
162
|
+
objectKey: string;
|
|
163
|
+
sha256: string;
|
|
164
|
+
sizeBytes: number;
|
|
165
|
+
};
|
|
166
|
+
result: 'integration_pg_dump_captured';
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export type RestoreIntegrationPgDumpCallbackResult = {
|
|
170
|
+
result: 'integration_pg_dump_restored';
|
|
171
|
+
validationMetadata: AppDatabaseImportValidationMetadata;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export type AppDatabaseImportCallbackResult =
|
|
175
|
+
| CaptureIntegrationPgDumpCallbackResult
|
|
176
|
+
| InspectImportPrefixCallbackResult
|
|
177
|
+
| PrepareImportPrefixUploadCallbackResult
|
|
178
|
+
| RestoreIntegrationPgDumpCallbackResult;
|
|
118
179
|
|
|
119
180
|
const importOperations = new Set<string>(APP_DATABASE_IMPORT_OPERATIONS);
|
|
120
181
|
|
|
@@ -131,8 +192,26 @@ export function isAppDatabaseImportDispatchPayload(
|
|
|
131
192
|
if (!isNonEmptyString(value.import.profileToken) || !isNonEmptyString(value.import.resourceToken)) {
|
|
132
193
|
return false;
|
|
133
194
|
}
|
|
195
|
+
if (value.operation === 'capture_integration_pg_dump') {
|
|
196
|
+
return (
|
|
197
|
+
hasOnlyKeys(value.import, ['importId', 'kind', 'profileToken', 'resourceToken', 'source']) &&
|
|
198
|
+
isAppDatabaseImportSource(value.import.source)
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (value.operation === 'restore_integration_pg_dump') {
|
|
202
|
+
return (
|
|
203
|
+
hasOnlyKeys(value.import, ['importId', 'kind', 'objectKey', 'profileToken', 'resourceToken', 'sha256', 'sizeBytes']) &&
|
|
204
|
+
isSafeObjectKey(value.import.objectKey) &&
|
|
205
|
+
isPositiveInteger(value.import.sizeBytes) &&
|
|
206
|
+
isSha256(value.import.sha256)
|
|
207
|
+
);
|
|
208
|
+
}
|
|
134
209
|
if (value.operation === 'prepare_import_prefix_upload') {
|
|
135
|
-
return
|
|
210
|
+
return (
|
|
211
|
+
hasOnlyKeys(value.import, ['expectedSizeBytes', 'importId', 'kind', 'prefixSizeBytes', 'profileToken', 'resourceToken']) &&
|
|
212
|
+
isPositiveInteger(value.import.expectedSizeBytes) &&
|
|
213
|
+
isPositiveInteger(value.import.prefixSizeBytes)
|
|
214
|
+
);
|
|
136
215
|
}
|
|
137
216
|
if (value.operation === 'restore_import_data') {
|
|
138
217
|
return (
|
|
@@ -144,7 +223,8 @@ export function isAppDatabaseImportDispatchPayload(
|
|
|
144
223
|
}
|
|
145
224
|
return (
|
|
146
225
|
value.operation === 'inspect_import_prefix' &&
|
|
147
|
-
value.import
|
|
226
|
+
hasOnlyKeys(value.import, ['importId', 'kind', 'prefixObjectKey', 'prefixSizeBytes', 'profileToken', 'resourceToken']) &&
|
|
227
|
+
isPositiveInteger(value.import.prefixSizeBytes) &&
|
|
148
228
|
isNonEmptyString(value.import.prefixObjectKey)
|
|
149
229
|
);
|
|
150
230
|
}
|
|
@@ -165,6 +245,22 @@ export function isAppDatabaseImportCallbackResult(value: unknown): value is AppD
|
|
|
165
245
|
value.nextPrefixSizeBytes > value.inspectedPrefixSizeBytes
|
|
166
246
|
);
|
|
167
247
|
}
|
|
248
|
+
if (value.result === 'integration_pg_dump_captured') {
|
|
249
|
+
return (
|
|
250
|
+
hasOnlyKeys(value, ['artifact', 'result']) &&
|
|
251
|
+
isUnknownRecord(value.artifact) &&
|
|
252
|
+
hasOnlyKeys(value.artifact, ['objectKey', 'sha256', 'sizeBytes']) &&
|
|
253
|
+
isSafeObjectKey(value.artifact.objectKey) &&
|
|
254
|
+
isPositiveInteger(value.artifact.sizeBytes) &&
|
|
255
|
+
isSha256(value.artifact.sha256)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
if (value.result === 'integration_pg_dump_restored') {
|
|
259
|
+
return (
|
|
260
|
+
hasOnlyKeys(value, ['result', 'validationMetadata']) &&
|
|
261
|
+
parseAppDatabaseImportValidationMetadata(value.validationMetadata, true) !== undefined
|
|
262
|
+
);
|
|
263
|
+
}
|
|
168
264
|
return (
|
|
169
265
|
value.result === 'schema_validated' &&
|
|
170
266
|
parseAppDatabaseImportValidationMetadata(value.validationMetadata) !== undefined &&
|
|
@@ -172,11 +268,27 @@ export function isAppDatabaseImportCallbackResult(value: unknown): value is AppD
|
|
|
172
268
|
);
|
|
173
269
|
}
|
|
174
270
|
|
|
271
|
+
export function isAppDatabaseImportSource(value: unknown): value is AppDatabaseImportSource {
|
|
272
|
+
return (
|
|
273
|
+
isUnknownRecord(value) &&
|
|
274
|
+
hasOnlyKeys(value, ['integrationConfigurationId', 'integrationId', 'profile', 'profileId', 'type']) &&
|
|
275
|
+
value.type === 'integration' &&
|
|
276
|
+
isNonEmptyString(value.integrationId) &&
|
|
277
|
+
isNonEmptyString(value.integrationConfigurationId) &&
|
|
278
|
+
isNonEmptyString(value.profileId) &&
|
|
279
|
+
isNonEmptyString(value.profile)
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
175
283
|
// Rebuild metadata from its allowlisted fields so archive contents, SQL, rows,
|
|
176
284
|
// and other worker-provided details cannot cross this contract boundary.
|
|
177
|
-
export function parseAppDatabaseImportValidationMetadata(
|
|
285
|
+
export function parseAppDatabaseImportValidationMetadata(
|
|
286
|
+
value: unknown,
|
|
287
|
+
requireExact = false
|
|
288
|
+
): AppDatabaseImportValidationMetadata | undefined {
|
|
178
289
|
if (
|
|
179
290
|
!isUnknownRecord(value) ||
|
|
291
|
+
(requireExact && !hasOnlyKeys(value, ['postgresMajorVersion', 'tableNames'])) ||
|
|
180
292
|
!isPositiveInteger(value.postgresMajorVersion) ||
|
|
181
293
|
!Array.isArray(value.tableNames) ||
|
|
182
294
|
!value.tableNames.every(isNonEmptyString)
|
|
@@ -203,7 +315,6 @@ function hasImportDispatchBase(value: Record<string, unknown>): value is ImportD
|
|
|
203
315
|
isUnknownRecord(value.import) &&
|
|
204
316
|
isNonEmptyString(value.import.importId) &&
|
|
205
317
|
value.import.kind === 'pg_dump_custom' &&
|
|
206
|
-
isPositiveInteger(value.import.prefixSizeBytes) &&
|
|
207
318
|
isNonEmptyString(value.organizationId) &&
|
|
208
319
|
isNonEmptyString(value.profile) &&
|
|
209
320
|
isNonEmptyString(value.requestId) &&
|
|
@@ -233,6 +344,21 @@ function isPositiveInteger(value: unknown): value is number {
|
|
|
233
344
|
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
234
345
|
}
|
|
235
346
|
|
|
347
|
+
function isSha256(value: unknown): value is string {
|
|
348
|
+
return typeof value === 'string' && /^[0-9a-f]{64}$/.test(value);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function isSafeObjectKey(value: unknown): value is string {
|
|
352
|
+
return (
|
|
353
|
+
typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._/-]{0,1023}$/.test(value) && !value.split('/').some((segment) => segment === '..')
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
236
357
|
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
237
358
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
238
359
|
}
|
|
360
|
+
|
|
361
|
+
function hasOnlyKeys(value: Record<string, unknown>, allowed: readonly string[]): boolean {
|
|
362
|
+
const allowedKeys = new Set(allowed);
|
|
363
|
+
return Object.keys(value).every((key) => allowedKeys.has(key));
|
|
364
|
+
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { ViewMode } from '../types/event/index.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
databaseWideOperationBlockReason,
|
|
6
|
+
isProductionDatabaseRetirementEligible,
|
|
7
|
+
preferredBlockingDatabaseOperation,
|
|
8
|
+
TABLE_IMPORT_IN_PROGRESS_REASON,
|
|
9
|
+
type AppDatabaseStatus,
|
|
10
|
+
type AppDatabaseStatusBinding
|
|
11
|
+
} from './appDatabaseStatus.js';
|
|
5
12
|
|
|
6
13
|
const binding: AppDatabaseStatusBinding = {
|
|
14
|
+
agentName: 'customer-opa',
|
|
7
15
|
bindingId: 'binding-1',
|
|
8
16
|
created: '2026-09-14T00:00:00.000Z',
|
|
9
17
|
instance: null,
|
|
@@ -103,3 +111,14 @@ describe('production database retirement eligibility', () => {
|
|
|
103
111
|
).toBe(false);
|
|
104
112
|
});
|
|
105
113
|
});
|
|
114
|
+
|
|
115
|
+
describe('database-wide operation block', () => {
|
|
116
|
+
it('prefers retirement over a migration and names the operation the import has to wait for', () => {
|
|
117
|
+
expect(preferredBlockingDatabaseOperation(['restore_import_data', 'migrate_schema', 'retire_database'])).toBe('retire_database');
|
|
118
|
+
expect(preferredBlockingDatabaseOperation(['import_table_data'])).toBeUndefined();
|
|
119
|
+
expect(databaseWideOperationBlockReason('migrate_schema')).toBe('A schema migration is running on this database.');
|
|
120
|
+
expect(databaseWideOperationBlockReason('restore_import_data')).toBe('A database restore is running on this data tag.');
|
|
121
|
+
expect(databaseWideOperationBlockReason('retire_database')).toBe('This database is being removed.');
|
|
122
|
+
expect(TABLE_IMPORT_IN_PROGRESS_REASON).toBe('A table import is running on this database.');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ViewMode } from '../types/event/index.js';
|
|
2
2
|
import type { AppDatabaseTagRef } from './appDatabaseCompatibility.js';
|
|
3
|
+
import type { AppDatabaseImportTagSupport } from './importCompatibility.js';
|
|
3
4
|
import type { DatabaseEngine, LifecycleMigrationState, LifecycleState } from './index.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -20,13 +21,46 @@ export type AppDatabaseStatusIntegration = {
|
|
|
20
21
|
pluginId: string;
|
|
21
22
|
};
|
|
22
23
|
|
|
24
|
+
// These rewrite or remove the whole database. A CSV import into any table on
|
|
25
|
+
// that binding has to wait until the lifecycle request leaves these states.
|
|
26
|
+
export const DATABASE_WIDE_LIFECYCLE_OPERATIONS = ['migrate_schema', 'restore_import_data', 'retire_database'] as const;
|
|
27
|
+
export type DatabaseWideLifecycleOperation = (typeof DATABASE_WIDE_LIFECYCLE_OPERATIONS)[number];
|
|
28
|
+
|
|
29
|
+
export const TABLE_IMPORT_IN_PROGRESS_REASON = 'A table import is running on this database.';
|
|
30
|
+
|
|
31
|
+
export function databaseWideOperationBlockReason(operation: DatabaseWideLifecycleOperation): string {
|
|
32
|
+
switch (operation) {
|
|
33
|
+
case 'migrate_schema':
|
|
34
|
+
return 'A schema migration is running on this database.';
|
|
35
|
+
case 'restore_import_data':
|
|
36
|
+
return 'A database restore is running on this data tag.';
|
|
37
|
+
case 'retire_database':
|
|
38
|
+
return 'This database is being removed.';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const BLOCKING_OPERATION_PRIORITY = ['retire_database', 'migrate_schema', 'restore_import_data'] as const;
|
|
43
|
+
|
|
44
|
+
export function preferredBlockingDatabaseOperation(operations: readonly string[]): DatabaseWideLifecycleOperation | undefined {
|
|
45
|
+
return BLOCKING_OPERATION_PRIORITY.find((operation) => operations.includes(operation));
|
|
46
|
+
}
|
|
47
|
+
|
|
23
48
|
export type AppDatabaseStatusBinding = {
|
|
49
|
+
/**
|
|
50
|
+
* Owning OPA deployment name (`databaseLifecycle:agentName`). Null on
|
|
51
|
+
* bindings created before ownership tracking. Schema reads route to this
|
|
52
|
+
* agent; without it the browser cannot authorize a describe call.
|
|
53
|
+
*/
|
|
54
|
+
agentName: string | null;
|
|
24
55
|
bindingId: string;
|
|
25
56
|
created: string;
|
|
26
57
|
instance: AppDatabaseStatusInstance | null;
|
|
27
58
|
integration: AppDatabaseStatusIntegration | null;
|
|
28
59
|
lastActivity: string;
|
|
29
60
|
lifecycleState: LifecycleState;
|
|
61
|
+
// Set while migrate_schema, restore_import_data, or retire_database is still
|
|
62
|
+
// pending or running for this binding.
|
|
63
|
+
inProgressOperation?: DatabaseWideLifecycleOperation;
|
|
30
64
|
logicalName: string;
|
|
31
65
|
migrationDirectory: string | null;
|
|
32
66
|
migrationState: LifecycleMigrationState;
|
|
@@ -35,6 +69,11 @@ export type AppDatabaseStatusBinding = {
|
|
|
35
69
|
export type AppDatabaseStatusTag = AppDatabaseTagRef & {
|
|
36
70
|
/** Null when this data tag has no live (non-cancelled) database. */
|
|
37
71
|
binding: AppDatabaseStatusBinding | null;
|
|
72
|
+
/**
|
|
73
|
+
* Whether this tag's registered worker can run a CSV import. Computed by
|
|
74
|
+
* the server from connected-agent registrations, not from a browser probe.
|
|
75
|
+
*/
|
|
76
|
+
csvImport?: AppDatabaseImportTagSupport;
|
|
38
77
|
/**
|
|
39
78
|
* True when this data tag had a database that was retired. The live
|
|
40
79
|
* binding is omitted so the tag can be provisioned again, but Status
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
csvImportPlanFromWizard,
|
|
4
5
|
mintDataImportConcurrencyKey,
|
|
5
6
|
parseDataImportMetadata,
|
|
6
7
|
parseDataImportTarget,
|
|
@@ -62,6 +63,12 @@ describe('data import contracts', () => {
|
|
|
62
63
|
}
|
|
63
64
|
expect(mintDataImportConcurrencyKey(spaced, target)).toBe(`csv:${target.bindingId}:public.orders`);
|
|
64
65
|
expect(mintDataImportConcurrencyKey(plain, target)).toBe(mintDataImportConcurrencyKey(spaced, target));
|
|
66
|
+
expect(mintDataImportConcurrencyKey({ kind: 'csv', mode: 'append', table: 'public.users' }, target)).not.toBe(
|
|
67
|
+
mintDataImportConcurrencyKey(plain, target)
|
|
68
|
+
);
|
|
69
|
+
expect(mintDataImportConcurrencyKey(plain, { ...target, bindingId: 'binding-production' })).not.toBe(
|
|
70
|
+
mintDataImportConcurrencyKey(plain, target)
|
|
71
|
+
);
|
|
65
72
|
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: ' ' })).toBeUndefined();
|
|
66
73
|
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: 'public. order items' })).toBeUndefined();
|
|
67
74
|
for (const table of ['PUBLIC.ORDERS', 'public."orders"', '"public"."orders"', 'orders']) {
|
|
@@ -80,6 +87,74 @@ describe('data import contracts', () => {
|
|
|
80
87
|
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: `public.${sixtyThree}y` })).toBeUndefined();
|
|
81
88
|
});
|
|
82
89
|
|
|
90
|
+
test('keeps a csv upload without a table plan and stores a complete plan', () => {
|
|
91
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: 'public.orders' })).toEqual({
|
|
92
|
+
kind: 'csv',
|
|
93
|
+
mode: 'append',
|
|
94
|
+
table: 'public.orders'
|
|
95
|
+
});
|
|
96
|
+
const plan = {
|
|
97
|
+
columnMapping: [{ source: 'id', target: 'id' }],
|
|
98
|
+
dateStyle: 'ISO, YMD',
|
|
99
|
+
delimiter: ',' as const,
|
|
100
|
+
emptyAsNull: true,
|
|
101
|
+
encoding: 'UTF8' as const,
|
|
102
|
+
hasHeader: true,
|
|
103
|
+
timeZone: 'UTC',
|
|
104
|
+
trimWhitespace: true
|
|
105
|
+
};
|
|
106
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'replace', table: 'public.orders', ...plan })).toEqual({
|
|
107
|
+
kind: 'csv',
|
|
108
|
+
mode: 'replace',
|
|
109
|
+
table: 'public.orders',
|
|
110
|
+
...plan
|
|
111
|
+
});
|
|
112
|
+
expect(
|
|
113
|
+
parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: 'public.superblocks_schema_migrations', ...plan })
|
|
114
|
+
).toBeUndefined();
|
|
115
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: 'public.superblocks_schema_migrations' })).toBeUndefined();
|
|
116
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: 'pg_catalog.pg_class', ...plan })).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('maps wizard date formats and encodings onto the worker plan', () => {
|
|
120
|
+
const mapped = csvImportPlanFromWizard({
|
|
121
|
+
columnMappings: { id: 'id', skipped: '' },
|
|
122
|
+
dateFormat: 'YYYY-MM-DD',
|
|
123
|
+
encoding: 'utf-8',
|
|
124
|
+
trimWhitespace: true
|
|
125
|
+
});
|
|
126
|
+
expect(mapped).toEqual({
|
|
127
|
+
plan: {
|
|
128
|
+
columnMapping: [{ source: 'id', target: 'id' }],
|
|
129
|
+
dateStyle: 'ISO, YMD',
|
|
130
|
+
delimiter: ',',
|
|
131
|
+
emptyAsNull: true,
|
|
132
|
+
encoding: 'UTF8',
|
|
133
|
+
hasHeader: true,
|
|
134
|
+
timeZone: 'UTC',
|
|
135
|
+
trimWhitespace: true
|
|
136
|
+
},
|
|
137
|
+
status: 'ok'
|
|
138
|
+
});
|
|
139
|
+
expect(
|
|
140
|
+
csvImportPlanFromWizard({
|
|
141
|
+
columnMappings: { id: 'id' },
|
|
142
|
+
dateFormat: 'MM/DD/YYYY',
|
|
143
|
+
encoding: 'windows-1252',
|
|
144
|
+
trimWhitespace: false
|
|
145
|
+
})
|
|
146
|
+
).toMatchObject({ plan: { dateStyle: 'ISO, MDY', encoding: 'WINDOWS1252', trimWhitespace: false }, status: 'ok' });
|
|
147
|
+
expect(
|
|
148
|
+
csvImportPlanFromWizard({ columnMappings: { id: 'id' }, dateFormat: 'YYYY-MM-DD', encoding: 'utf-16', trimWhitespace: true })
|
|
149
|
+
).toEqual({ message: 'Encoding must be UTF-8 or Windows-1252.', status: 'invalid' });
|
|
150
|
+
expect(
|
|
151
|
+
csvImportPlanFromWizard({ columnMappings: { id: '' }, dateFormat: 'not-a-date', encoding: 'utf-8', trimWhitespace: true })
|
|
152
|
+
).toEqual({
|
|
153
|
+
message: 'Date format must be YYYY-MM-DD, MM/DD/YYYY, DD/MM/YYYY, or DD.MM.YYYY.',
|
|
154
|
+
status: 'invalid'
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
83
158
|
test('rejects zero prefix sizes and a zero postgres major', () => {
|
|
84
159
|
const base = { kind: 'pg_dump_custom' as const };
|
|
85
160
|
expect(parseDataImportMetadata('pg_dump_custom', { ...base, currentPrefixSizeBytes: 0 })).toBeUndefined();
|