@superblocksteam/shared 0.9614.3 → 0.9615.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/appDatabaseCompatibility.d.ts +5 -3
- package/dist/database-lifecycle/appDatabaseCompatibility.d.ts.map +1 -1
- package/dist/database-lifecycle/appDatabaseCompatibility.js.map +1 -1
- package/dist/database-lifecycle/appDatabaseImport.d.ts +70 -0
- package/dist/database-lifecycle/appDatabaseImport.d.ts.map +1 -0
- package/dist/database-lifecycle/appDatabaseImport.js +111 -0
- package/dist/database-lifecycle/appDatabaseImport.js.map +1 -0
- package/dist/database-lifecycle/appDatabaseImport.test.d.ts +2 -0
- package/dist/database-lifecycle/appDatabaseImport.test.d.ts.map +1 -0
- package/dist/database-lifecycle/appDatabaseImport.test.js +145 -0
- package/dist/database-lifecycle/appDatabaseImport.test.js.map +1 -0
- package/dist/database-lifecycle/appDatabaseStatus.d.ts +79 -0
- package/dist/database-lifecycle/appDatabaseStatus.d.ts.map +1 -0
- package/dist/database-lifecycle/appDatabaseStatus.js +28 -0
- package/dist/database-lifecycle/appDatabaseStatus.js.map +1 -0
- package/dist/database-lifecycle/appDatabaseStatus.test.d.ts +2 -0
- package/dist/database-lifecycle/appDatabaseStatus.test.d.ts.map +1 -0
- package/dist/database-lifecycle/appDatabaseStatus.test.js +88 -0
- package/dist/database-lifecycle/appDatabaseStatus.test.js.map +1 -0
- package/dist/database-lifecycle/dataImport.d.ts +53 -0
- package/dist/database-lifecycle/dataImport.d.ts.map +1 -0
- package/dist/database-lifecycle/dataImport.js +173 -0
- package/dist/database-lifecycle/dataImport.js.map +1 -0
- package/dist/database-lifecycle/dataImport.test.d.ts +2 -0
- package/dist/database-lifecycle/dataImport.test.d.ts.map +1 -0
- package/dist/database-lifecycle/dataImport.test.js +84 -0
- package/dist/database-lifecycle/dataImport.test.js.map +1 -0
- package/dist/database-lifecycle/index.d.ts +101 -5
- package/dist/database-lifecycle/index.d.ts.map +1 -1
- package/dist/database-lifecycle/index.js +55 -6
- package/dist/database-lifecycle/index.js.map +1 -1
- package/dist/database-lifecycle/index.test.js +18 -6
- 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/plugins/templates/workflow.d.ts.map +1 -1
- package/dist/plugins/templates/workflow.js +1 -3
- package/dist/plugins/templates/workflow.js.map +1 -1
- package/dist/socket/protocol.d.ts +19 -2
- package/dist/socket/protocol.d.ts.map +1 -1
- package/dist/socket/protocol.js.map +1 -1
- package/dist/types/ai/index.d.ts +39 -0
- package/dist/types/ai/index.d.ts.map +1 -1
- package/dist/types/ai/index.js +8 -1
- package/dist/types/ai/index.js.map +1 -1
- package/dist/types/billing/usageSource.d.ts +1 -0
- package/dist/types/billing/usageSource.d.ts.map +1 -1
- package/dist/types/billing/usageSource.js +19 -1
- package/dist/types/billing/usageSource.js.map +1 -1
- package/dist/types/event/index.d.ts +6 -1
- package/dist/types/event/index.d.ts.map +1 -1
- package/dist/types/event/index.js +5 -0
- package/dist/types/event/index.js.map +1 -1
- package/dist/types/policyGate/index.d.ts +11 -0
- package/dist/types/policyGate/index.d.ts.map +1 -1
- package/dist/types/policyGate/index.js +9 -0
- package/dist/types/policyGate/index.js.map +1 -1
- package/dist/utils/attachment-delivery.js +1 -1
- package/dist/utils/attachment-delivery.js.map +1 -1
- package/dist/utils/attachment-delivery.test.js +11 -0
- package/dist/utils/attachment-delivery.test.js.map +1 -1
- package/dist/utils/attachment-upload.d.ts +15 -0
- package/dist/utils/attachment-upload.d.ts.map +1 -1
- package/dist/utils/attachment-upload.js +44 -2
- package/dist/utils/attachment-upload.js.map +1 -1
- package/dist/utils/attachment-upload.test.js +37 -1
- package/dist/utils/attachment-upload.test.js.map +1 -1
- package/dist/utils/attachment-validation.d.ts +18 -1
- package/dist/utils/attachment-validation.d.ts.map +1 -1
- package/dist/utils/attachment-validation.js +38 -5
- package/dist/utils/attachment-validation.js.map +1 -1
- package/dist/utils/attachment-validation.test.js +22 -3
- package/dist/utils/attachment-validation.test.js.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.d.ts +5 -3
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.js.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseImport.d.ts +70 -0
- package/dist-esm/database-lifecycle/appDatabaseImport.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseImport.js +104 -0
- package/dist-esm/database-lifecycle/appDatabaseImport.js.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseImport.test.d.ts +2 -0
- package/dist-esm/database-lifecycle/appDatabaseImport.test.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseImport.test.js +143 -0
- package/dist-esm/database-lifecycle/appDatabaseImport.test.js.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseStatus.d.ts +79 -0
- package/dist-esm/database-lifecycle/appDatabaseStatus.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseStatus.js +23 -0
- package/dist-esm/database-lifecycle/appDatabaseStatus.js.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseStatus.test.d.ts +2 -0
- package/dist-esm/database-lifecycle/appDatabaseStatus.test.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseStatus.test.js +86 -0
- package/dist-esm/database-lifecycle/appDatabaseStatus.test.js.map +1 -0
- package/dist-esm/database-lifecycle/dataImport.d.ts +53 -0
- package/dist-esm/database-lifecycle/dataImport.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/dataImport.js +165 -0
- package/dist-esm/database-lifecycle/dataImport.js.map +1 -0
- package/dist-esm/database-lifecycle/dataImport.test.d.ts +2 -0
- package/dist-esm/database-lifecycle/dataImport.test.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/dataImport.test.js +82 -0
- package/dist-esm/database-lifecycle/dataImport.test.js.map +1 -0
- package/dist-esm/database-lifecycle/index.d.ts +101 -5
- package/dist-esm/database-lifecycle/index.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/index.js +36 -5
- package/dist-esm/database-lifecycle/index.js.map +1 -1
- package/dist-esm/database-lifecycle/index.test.js +19 -7
- 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/plugins/templates/workflow.d.ts.map +1 -1
- package/dist-esm/plugins/templates/workflow.js +1 -3
- package/dist-esm/plugins/templates/workflow.js.map +1 -1
- package/dist-esm/socket/protocol.d.ts +19 -2
- package/dist-esm/socket/protocol.d.ts.map +1 -1
- package/dist-esm/socket/protocol.js.map +1 -1
- package/dist-esm/types/ai/index.d.ts +39 -0
- package/dist-esm/types/ai/index.d.ts.map +1 -1
- package/dist-esm/types/ai/index.js +6 -0
- package/dist-esm/types/ai/index.js.map +1 -1
- package/dist-esm/types/billing/usageSource.d.ts +1 -0
- package/dist-esm/types/billing/usageSource.d.ts.map +1 -1
- package/dist-esm/types/billing/usageSource.js +18 -0
- package/dist-esm/types/billing/usageSource.js.map +1 -1
- package/dist-esm/types/event/index.d.ts +6 -1
- package/dist-esm/types/event/index.d.ts.map +1 -1
- package/dist-esm/types/event/index.js +5 -0
- package/dist-esm/types/event/index.js.map +1 -1
- package/dist-esm/types/policyGate/index.d.ts +11 -0
- package/dist-esm/types/policyGate/index.d.ts.map +1 -1
- package/dist-esm/types/policyGate/index.js +8 -0
- package/dist-esm/types/policyGate/index.js.map +1 -1
- package/dist-esm/utils/attachment-delivery.js +2 -2
- package/dist-esm/utils/attachment-delivery.js.map +1 -1
- package/dist-esm/utils/attachment-delivery.test.js +11 -0
- package/dist-esm/utils/attachment-delivery.test.js.map +1 -1
- package/dist-esm/utils/attachment-upload.d.ts +15 -0
- package/dist-esm/utils/attachment-upload.d.ts.map +1 -1
- package/dist-esm/utils/attachment-upload.js +41 -1
- package/dist-esm/utils/attachment-upload.js.map +1 -1
- package/dist-esm/utils/attachment-upload.test.js +38 -2
- package/dist-esm/utils/attachment-upload.test.js.map +1 -1
- package/dist-esm/utils/attachment-validation.d.ts +18 -1
- package/dist-esm/utils/attachment-validation.d.ts.map +1 -1
- package/dist-esm/utils/attachment-validation.js +38 -5
- package/dist-esm/utils/attachment-validation.js.map +1 -1
- package/dist-esm/utils/attachment-validation.test.js +23 -4
- package/dist-esm/utils/attachment-validation.test.js.map +1 -1
- package/package.json +1 -1
- package/src/database-lifecycle/appDatabaseCompatibility.ts +6 -3
- package/src/database-lifecycle/appDatabaseImport.test.ts +185 -0
- package/src/database-lifecycle/appDatabaseImport.ts +205 -0
- package/src/database-lifecycle/appDatabaseStatus.test.ts +105 -0
- package/src/database-lifecycle/appDatabaseStatus.ts +95 -0
- package/src/database-lifecycle/dataImport.test.ts +95 -0
- package/src/database-lifecycle/dataImport.ts +244 -0
- package/src/database-lifecycle/index.test.ts +20 -6
- package/src/database-lifecycle/index.ts +203 -7
- package/src/index.ts +1 -0
- package/src/plugins/templates/workflow.ts +1 -3
- package/src/socket/protocol.ts +14 -2
- package/src/types/ai/index.ts +39 -0
- package/src/types/billing/usageSource.ts +20 -0
- package/src/types/event/index.ts +6 -1
- package/src/types/policyGate/index.ts +14 -0
- package/src/utils/attachment-delivery.test.ts +14 -0
- package/src/utils/attachment-delivery.ts +2 -2
- package/src/utils/attachment-upload.test.ts +48 -1
- package/src/utils/attachment-upload.ts +48 -1
- package/src/utils/attachment-validation.test.ts +35 -3
- package/src/utils/attachment-validation.ts +52 -6
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isAppDatabaseImportCallbackResult,
|
|
5
|
+
isAppDatabaseImportDispatchPayload,
|
|
6
|
+
parseAppDatabaseImportValidationMetadata
|
|
7
|
+
} from './appDatabaseImport.js';
|
|
8
|
+
import type { AppDatabaseImportCallback } from './index.js';
|
|
9
|
+
|
|
10
|
+
describe('App Database import contracts', () => {
|
|
11
|
+
test('validates operation-specific dispatch fields', () => {
|
|
12
|
+
const base = {
|
|
13
|
+
applicationId: 'application-id',
|
|
14
|
+
bindingId: 'binding-id',
|
|
15
|
+
bindingKey: 'binding-key',
|
|
16
|
+
desiredSpec: { engine: 'postgres', logicalName: 'Orders DB' },
|
|
17
|
+
desiredSpecHash: 'desired-spec-hash',
|
|
18
|
+
import: {
|
|
19
|
+
importId: 'import-id',
|
|
20
|
+
kind: 'pg_dump_custom',
|
|
21
|
+
prefixSizeBytes: 1_024,
|
|
22
|
+
profileToken: 'profile-token',
|
|
23
|
+
resourceToken: 'resource-token'
|
|
24
|
+
},
|
|
25
|
+
organizationId: 'organization-id',
|
|
26
|
+
profile: 'production',
|
|
27
|
+
requestId: 'request-id',
|
|
28
|
+
resourceKey: 'organization/application/database/profile/default'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
expect(
|
|
32
|
+
isAppDatabaseImportDispatchPayload({
|
|
33
|
+
...base,
|
|
34
|
+
import: { ...base.import, expectedSizeBytes: 10_000 },
|
|
35
|
+
operation: 'prepare_import_prefix_upload'
|
|
36
|
+
})
|
|
37
|
+
).toBe(true);
|
|
38
|
+
expect(
|
|
39
|
+
isAppDatabaseImportDispatchPayload({
|
|
40
|
+
...base,
|
|
41
|
+
import: base.import,
|
|
42
|
+
operation: 'prepare_import_prefix_upload'
|
|
43
|
+
})
|
|
44
|
+
).toBe(false);
|
|
45
|
+
expect(
|
|
46
|
+
isAppDatabaseImportDispatchPayload({
|
|
47
|
+
...base,
|
|
48
|
+
import: {
|
|
49
|
+
...base.import,
|
|
50
|
+
expectedSizeBytes: 10_000,
|
|
51
|
+
prefixObjectKey: 'imports/import-id/prefix'
|
|
52
|
+
},
|
|
53
|
+
operation: 'prepare_import_prefix_upload'
|
|
54
|
+
})
|
|
55
|
+
).toBe(false);
|
|
56
|
+
expect(
|
|
57
|
+
isAppDatabaseImportDispatchPayload({
|
|
58
|
+
...base,
|
|
59
|
+
import: { ...base.import, prefixObjectKey: 'imports/import-id/prefix' },
|
|
60
|
+
operation: 'inspect_import_prefix'
|
|
61
|
+
})
|
|
62
|
+
).toBe(true);
|
|
63
|
+
expect(
|
|
64
|
+
isAppDatabaseImportDispatchPayload({
|
|
65
|
+
...base,
|
|
66
|
+
import: {
|
|
67
|
+
...base.import,
|
|
68
|
+
expectedSizeBytes: 10_000,
|
|
69
|
+
prefixObjectKey: 'imports/import-id/prefix'
|
|
70
|
+
},
|
|
71
|
+
operation: 'inspect_import_prefix'
|
|
72
|
+
})
|
|
73
|
+
).toBe(false);
|
|
74
|
+
expect({
|
|
75
|
+
...base,
|
|
76
|
+
import: { ...base.import, expectedSizeBytes: 10_000 },
|
|
77
|
+
operation: 'prepare_import_prefix_upload'
|
|
78
|
+
}).not.toHaveProperty('import.prefixObjectKey');
|
|
79
|
+
expect(
|
|
80
|
+
isAppDatabaseImportDispatchPayload({
|
|
81
|
+
...base,
|
|
82
|
+
operation: 'inspect_import_prefix'
|
|
83
|
+
})
|
|
84
|
+
).toBe(false);
|
|
85
|
+
expect(
|
|
86
|
+
isAppDatabaseImportDispatchPayload({
|
|
87
|
+
...base,
|
|
88
|
+
import: { ...base.import, profileToken: '' },
|
|
89
|
+
operation: 'prepare_import_prefix_upload'
|
|
90
|
+
})
|
|
91
|
+
).toBe(false);
|
|
92
|
+
expect(
|
|
93
|
+
isAppDatabaseImportDispatchPayload({
|
|
94
|
+
...base,
|
|
95
|
+
import: { ...base.import, resourceToken: '' },
|
|
96
|
+
operation: 'prepare_import_prefix_upload'
|
|
97
|
+
})
|
|
98
|
+
).toBe(false);
|
|
99
|
+
const withoutOrganization: Record<string, unknown> = { ...base };
|
|
100
|
+
delete withoutOrganization.organizationId;
|
|
101
|
+
expect(
|
|
102
|
+
isAppDatabaseImportDispatchPayload({
|
|
103
|
+
...withoutOrganization,
|
|
104
|
+
import: { ...base.import, expectedSizeBytes: 10_000 },
|
|
105
|
+
operation: 'prepare_import_prefix_upload'
|
|
106
|
+
})
|
|
107
|
+
).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('requires prefix growth and narrow nested callback results', () => {
|
|
111
|
+
expect(
|
|
112
|
+
isAppDatabaseImportCallbackResult({
|
|
113
|
+
inspectedPrefixSizeBytes: 1_024,
|
|
114
|
+
nextPrefixSizeBytes: 2_048,
|
|
115
|
+
result: 'prefix_incomplete'
|
|
116
|
+
})
|
|
117
|
+
).toBe(true);
|
|
118
|
+
expect(
|
|
119
|
+
isAppDatabaseImportCallbackResult({
|
|
120
|
+
inspectedPrefixSizeBytes: 1_024,
|
|
121
|
+
nextPrefixSizeBytes: 1_024,
|
|
122
|
+
result: 'prefix_incomplete'
|
|
123
|
+
})
|
|
124
|
+
).toBe(false);
|
|
125
|
+
expect(
|
|
126
|
+
isAppDatabaseImportCallbackResult({
|
|
127
|
+
prefixObjectKey: 'imports/import-id/prefix',
|
|
128
|
+
prefixUploadUrl: 'https://uploads.example/prefix',
|
|
129
|
+
result: 'prefix_upload_url'
|
|
130
|
+
})
|
|
131
|
+
).toBe(true);
|
|
132
|
+
expect(
|
|
133
|
+
isAppDatabaseImportCallbackResult({
|
|
134
|
+
prefixUploadUrl: 'https://uploads.example/prefix',
|
|
135
|
+
result: 'prefix_upload_url'
|
|
136
|
+
})
|
|
137
|
+
).toBe(false);
|
|
138
|
+
|
|
139
|
+
const callback: AppDatabaseImportCallback = {
|
|
140
|
+
bindingKey: 'binding-key',
|
|
141
|
+
import: {
|
|
142
|
+
prefixObjectKey: 'imports/import-id/prefix',
|
|
143
|
+
prefixUploadUrl: 'https://uploads.example/prefix',
|
|
144
|
+
result: 'prefix_upload_url'
|
|
145
|
+
},
|
|
146
|
+
lifecycleState: 'ready',
|
|
147
|
+
requestId: 'request-id'
|
|
148
|
+
};
|
|
149
|
+
if (callback.import.result !== 'prefix_upload_url') {
|
|
150
|
+
throw new Error('Expected prefix upload URL callback');
|
|
151
|
+
}
|
|
152
|
+
expect(callback.import.prefixUploadUrl).toContain('https://');
|
|
153
|
+
expect(
|
|
154
|
+
isAppDatabaseImportCallbackResult({
|
|
155
|
+
result: 'schema_validated',
|
|
156
|
+
validationMetadata: {
|
|
157
|
+
postgresMajorVersion: 16,
|
|
158
|
+
tableNames: ['public.orders']
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
).toBe(true);
|
|
162
|
+
expect(
|
|
163
|
+
isAppDatabaseImportCallbackResult({
|
|
164
|
+
postgresMajorVersion: 16,
|
|
165
|
+
result: 'schema_validated',
|
|
166
|
+
tableNames: ['public.orders']
|
|
167
|
+
})
|
|
168
|
+
).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('retains only allowlisted validation metadata', () => {
|
|
172
|
+
expect(
|
|
173
|
+
parseAppDatabaseImportValidationMetadata({
|
|
174
|
+
postgresMajorVersion: 16,
|
|
175
|
+
rows: [{ secret: 'value' }],
|
|
176
|
+
sql: 'CREATE TABLE private_data',
|
|
177
|
+
tableNames: ['public.orders', 'public.customers']
|
|
178
|
+
})
|
|
179
|
+
).toEqual({
|
|
180
|
+
postgresMajorVersion: 16,
|
|
181
|
+
tableNames: ['public.orders', 'public.customers']
|
|
182
|
+
});
|
|
183
|
+
expect(parseAppDatabaseImportValidationMetadata({ postgresMajorVersion: 16, tableNames: [''] })).toBeUndefined();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { DataImportKind, DataImportState } from './dataImport.js';
|
|
2
|
+
|
|
3
|
+
export const APP_DATABASE_IMPORT_OPERATIONS = ['prepare_import_prefix_upload', 'inspect_import_prefix'] as const;
|
|
4
|
+
// Subset of DATA_IMPORT_STATES. Prefix success is schema_validated, not ready.
|
|
5
|
+
export const APP_DATABASE_IMPORT_STATES = [
|
|
6
|
+
'requested',
|
|
7
|
+
'preparing_prefix_upload',
|
|
8
|
+
'awaiting_prefix_upload',
|
|
9
|
+
'validating_schema',
|
|
10
|
+
'schema_validated',
|
|
11
|
+
'failed',
|
|
12
|
+
'cancelled'
|
|
13
|
+
] as const satisfies readonly DataImportState[];
|
|
14
|
+
export const APP_DATABASE_IMPORT_ERROR_CODES = [
|
|
15
|
+
'prefix_incomplete',
|
|
16
|
+
'schema_mismatch',
|
|
17
|
+
'unsupported_archive_format',
|
|
18
|
+
'archive_corrupt',
|
|
19
|
+
'postgres_version_newer_than_target',
|
|
20
|
+
'unsupported_object',
|
|
21
|
+
'schema_not_aligned',
|
|
22
|
+
'missing_bucket_access',
|
|
23
|
+
'internal',
|
|
24
|
+
'invalid_input',
|
|
25
|
+
'size_mismatch'
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
export type AppDatabaseImportKind = Extract<DataImportKind, 'pg_dump_custom'>;
|
|
29
|
+
export type AppDatabaseImportLifecycleOperation = (typeof APP_DATABASE_IMPORT_OPERATIONS)[number];
|
|
30
|
+
export type AppDatabaseImportState = (typeof APP_DATABASE_IMPORT_STATES)[number];
|
|
31
|
+
export type AppDatabaseImportErrorCode = (typeof APP_DATABASE_IMPORT_ERROR_CODES)[number];
|
|
32
|
+
|
|
33
|
+
export type AppDatabaseImportValidationMetadata = {
|
|
34
|
+
postgresMajorVersion: number;
|
|
35
|
+
tableNames: string[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type AppDatabaseImportDispatchDetails = {
|
|
39
|
+
importId: string;
|
|
40
|
+
kind: AppDatabaseImportKind;
|
|
41
|
+
prefixSizeBytes: number;
|
|
42
|
+
profileToken: string;
|
|
43
|
+
resourceToken: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type AppDatabaseImportDispatchBase<TDesiredSpec> = {
|
|
47
|
+
applicationId: string;
|
|
48
|
+
bindingId: string;
|
|
49
|
+
bindingKey: string;
|
|
50
|
+
connectionMetadata?: Record<string, string | number | boolean>;
|
|
51
|
+
continuation?: never;
|
|
52
|
+
desiredSpec: TDesiredSpec;
|
|
53
|
+
desiredSpecHash: string;
|
|
54
|
+
environment?: never;
|
|
55
|
+
metadata?: never;
|
|
56
|
+
migrations?: never;
|
|
57
|
+
organizationId: string;
|
|
58
|
+
profile: string;
|
|
59
|
+
provisioningId?: never;
|
|
60
|
+
requestId: string;
|
|
61
|
+
resourceKey: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type PrepareImportPrefixUploadDispatchPayloadEnvelope<TDesiredSpec> = AppDatabaseImportDispatchBase<TDesiredSpec> & {
|
|
65
|
+
import: AppDatabaseImportDispatchDetails & {
|
|
66
|
+
expectedSizeBytes: number;
|
|
67
|
+
};
|
|
68
|
+
operation: 'prepare_import_prefix_upload';
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type InspectImportPrefixDispatchPayloadEnvelope<TDesiredSpec> = AppDatabaseImportDispatchBase<TDesiredSpec> & {
|
|
72
|
+
import: AppDatabaseImportDispatchDetails & {
|
|
73
|
+
prefixObjectKey: string;
|
|
74
|
+
};
|
|
75
|
+
operation: 'inspect_import_prefix';
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type AppDatabaseImportDispatchPayloadEnvelope<TDesiredSpec> =
|
|
79
|
+
| InspectImportPrefixDispatchPayloadEnvelope<TDesiredSpec>
|
|
80
|
+
| PrepareImportPrefixUploadDispatchPayloadEnvelope<TDesiredSpec>;
|
|
81
|
+
|
|
82
|
+
export type PrepareImportPrefixUploadCallbackResult = {
|
|
83
|
+
prefixObjectKey: string;
|
|
84
|
+
prefixUploadHeaders?: Record<string, string>;
|
|
85
|
+
prefixUploadUrl: string;
|
|
86
|
+
result: 'prefix_upload_url';
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type InspectImportPrefixCallbackResult =
|
|
90
|
+
| {
|
|
91
|
+
inspectedPrefixSizeBytes: number;
|
|
92
|
+
nextPrefixSizeBytes: number;
|
|
93
|
+
result: 'prefix_incomplete';
|
|
94
|
+
}
|
|
95
|
+
| {
|
|
96
|
+
result: 'schema_validated';
|
|
97
|
+
validationMetadata: AppDatabaseImportValidationMetadata;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type AppDatabaseImportCallbackResult = InspectImportPrefixCallbackResult | PrepareImportPrefixUploadCallbackResult;
|
|
101
|
+
|
|
102
|
+
const importOperations = new Set<string>(APP_DATABASE_IMPORT_OPERATIONS);
|
|
103
|
+
|
|
104
|
+
export function isAppDatabaseImportLifecycleOperation(value: string): value is AppDatabaseImportLifecycleOperation {
|
|
105
|
+
return importOperations.has(value);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function isAppDatabaseImportDispatchPayload(
|
|
109
|
+
value: unknown
|
|
110
|
+
): value is AppDatabaseImportDispatchPayloadEnvelope<Record<string, unknown>> {
|
|
111
|
+
if (!isUnknownRecord(value) || !hasImportDispatchBase(value)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (!isNonEmptyString(value.import.profileToken) || !isNonEmptyString(value.import.resourceToken)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (value.operation === 'prepare_import_prefix_upload') {
|
|
118
|
+
return value.import.prefixObjectKey === undefined && isPositiveInteger(value.import.expectedSizeBytes);
|
|
119
|
+
}
|
|
120
|
+
return (
|
|
121
|
+
value.operation === 'inspect_import_prefix' &&
|
|
122
|
+
value.import.expectedSizeBytes === undefined &&
|
|
123
|
+
isNonEmptyString(value.import.prefixObjectKey)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function isAppDatabaseImportCallbackResult(value: unknown): value is AppDatabaseImportCallbackResult {
|
|
128
|
+
if (!isUnknownRecord(value)) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
if (value.result === 'prefix_upload_url') {
|
|
132
|
+
return (
|
|
133
|
+
isNonEmptyString(value.prefixObjectKey) && isNonEmptyString(value.prefixUploadUrl) && isOptionalStringMap(value.prefixUploadHeaders)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (value.result === 'prefix_incomplete') {
|
|
137
|
+
return (
|
|
138
|
+
isPositiveInteger(value.inspectedPrefixSizeBytes) &&
|
|
139
|
+
isPositiveInteger(value.nextPrefixSizeBytes) &&
|
|
140
|
+
value.nextPrefixSizeBytes > value.inspectedPrefixSizeBytes
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return value.result === 'schema_validated' && parseAppDatabaseImportValidationMetadata(value.validationMetadata) !== undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Rebuild metadata from its allowlisted fields so archive contents, SQL, rows,
|
|
147
|
+
// and other worker-provided details cannot cross this contract boundary.
|
|
148
|
+
export function parseAppDatabaseImportValidationMetadata(value: unknown): AppDatabaseImportValidationMetadata | undefined {
|
|
149
|
+
if (
|
|
150
|
+
!isUnknownRecord(value) ||
|
|
151
|
+
!isPositiveInteger(value.postgresMajorVersion) ||
|
|
152
|
+
!Array.isArray(value.tableNames) ||
|
|
153
|
+
!value.tableNames.every(isNonEmptyString)
|
|
154
|
+
) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
postgresMajorVersion: value.postgresMajorVersion,
|
|
159
|
+
tableNames: [...value.tableNames]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type ImportDispatchRecord = Record<string, unknown> & {
|
|
164
|
+
import: Record<string, unknown>;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function hasImportDispatchBase(value: Record<string, unknown>): value is ImportDispatchRecord {
|
|
168
|
+
return (
|
|
169
|
+
isNonEmptyString(value.applicationId) &&
|
|
170
|
+
isNonEmptyString(value.bindingId) &&
|
|
171
|
+
isNonEmptyString(value.bindingKey) &&
|
|
172
|
+
isUnknownRecord(value.desiredSpec) &&
|
|
173
|
+
isNonEmptyString(value.desiredSpecHash) &&
|
|
174
|
+
isUnknownRecord(value.import) &&
|
|
175
|
+
isNonEmptyString(value.import.importId) &&
|
|
176
|
+
value.import.kind === 'pg_dump_custom' &&
|
|
177
|
+
isPositiveInteger(value.import.prefixSizeBytes) &&
|
|
178
|
+
isNonEmptyString(value.organizationId) &&
|
|
179
|
+
isNonEmptyString(value.profile) &&
|
|
180
|
+
isNonEmptyString(value.requestId) &&
|
|
181
|
+
isNonEmptyString(value.resourceKey)
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isOptionalStringMap(value: unknown): value is Record<string, string> | undefined {
|
|
186
|
+
if (value === undefined) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
if (!isUnknownRecord(value)) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return Object.entries(value).every(([key, entry]) => key.length > 0 && typeof entry === 'string' && entry.length > 0);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
196
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isPositiveInteger(value: unknown): value is number {
|
|
200
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
204
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
205
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { ViewMode } from '../types/event/index.js';
|
|
4
|
+
import { isProductionDatabaseRetirementEligible, type AppDatabaseStatus, type AppDatabaseStatusBinding } from './appDatabaseStatus.js';
|
|
5
|
+
|
|
6
|
+
const binding: AppDatabaseStatusBinding = {
|
|
7
|
+
bindingId: 'binding-1',
|
|
8
|
+
created: '2026-09-14T00:00:00.000Z',
|
|
9
|
+
instance: null,
|
|
10
|
+
integration: null,
|
|
11
|
+
lastActivity: '2026-09-14T00:00:00.000Z',
|
|
12
|
+
lifecycleState: 'ready',
|
|
13
|
+
logicalName: 'app_db',
|
|
14
|
+
migrationDirectory: null,
|
|
15
|
+
migrationState: 'migrated'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function status(overrides: Partial<AppDatabaseStatus> = {}): AppDatabaseStatus {
|
|
19
|
+
return {
|
|
20
|
+
applicationId: 'app-1',
|
|
21
|
+
hasAppDatabase: false,
|
|
22
|
+
hasDatabaseCode: false,
|
|
23
|
+
liveVersionHasAppDatabase: false,
|
|
24
|
+
tags: [
|
|
25
|
+
{
|
|
26
|
+
availableModes: [ViewMode.DEPLOYED],
|
|
27
|
+
binding,
|
|
28
|
+
deleted: false,
|
|
29
|
+
displayName: 'Production',
|
|
30
|
+
isDefaultEditor: false,
|
|
31
|
+
profileKey: 'production'
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
...overrides
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('production database retirement eligibility', () => {
|
|
39
|
+
it('offers deletion after a live version that does not need a database and Edit leftovers are gone', () => {
|
|
40
|
+
expect(isProductionDatabaseRetirementEligible(status())).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('refuses deletion while the live version still needs a database', () => {
|
|
44
|
+
expect(isProductionDatabaseRetirementEligible(status({ liveVersionHasAppDatabase: true }))).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('refuses deletion while an Edit-only database remains', () => {
|
|
48
|
+
expect(
|
|
49
|
+
isProductionDatabaseRetirementEligible(
|
|
50
|
+
status({
|
|
51
|
+
tags: [
|
|
52
|
+
...status().tags,
|
|
53
|
+
{
|
|
54
|
+
availableModes: [ViewMode.EDITOR],
|
|
55
|
+
binding: { ...binding, bindingId: 'edit-binding' },
|
|
56
|
+
deleted: false,
|
|
57
|
+
displayName: 'Staging',
|
|
58
|
+
isDefaultEditor: true,
|
|
59
|
+
profileKey: 'staging'
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
|
+
).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('offers deletion of an unassigned leftover after a live version that does not need a database', () => {
|
|
68
|
+
expect(
|
|
69
|
+
isProductionDatabaseRetirementEligible(
|
|
70
|
+
status({
|
|
71
|
+
tags: [
|
|
72
|
+
{
|
|
73
|
+
availableModes: [],
|
|
74
|
+
binding,
|
|
75
|
+
deleted: false,
|
|
76
|
+
displayName: 'Removed production',
|
|
77
|
+
isDefaultEditor: false,
|
|
78
|
+
profileKey: 'removed-production'
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('refuses deletion while the editor default is still live, even when that tag is also deploy-allowed', () => {
|
|
87
|
+
expect(
|
|
88
|
+
isProductionDatabaseRetirementEligible(
|
|
89
|
+
status({
|
|
90
|
+
tags: [
|
|
91
|
+
...status().tags,
|
|
92
|
+
{
|
|
93
|
+
availableModes: [ViewMode.EDITOR, ViewMode.DEPLOYED],
|
|
94
|
+
binding: { ...binding, bindingId: 'edit-binding' },
|
|
95
|
+
deleted: false,
|
|
96
|
+
displayName: 'Staging',
|
|
97
|
+
isDefaultEditor: true,
|
|
98
|
+
profileKey: 'staging'
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
})
|
|
102
|
+
)
|
|
103
|
+
).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ViewMode } from '../types/event/index.js';
|
|
2
|
+
import type { AppDatabaseTagRef } from './appDatabaseCompatibility.js';
|
|
3
|
+
import type { DatabaseEngine, LifecycleMigrationState, LifecycleState } from './index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builder-safe physical instance fields. Pool capacity and the internal
|
|
7
|
+
* instance id stay off this payload: they are org-wide admin facts, and
|
|
8
|
+
* capacity.used counts every application in the org.
|
|
9
|
+
*/
|
|
10
|
+
export type AppDatabaseStatusInstance = {
|
|
11
|
+
engine: DatabaseEngine;
|
|
12
|
+
engineVersion: string | null;
|
|
13
|
+
region: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type AppDatabaseStatusIntegration = {
|
|
17
|
+
apiNames: string[];
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
pluginId: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type AppDatabaseStatusBinding = {
|
|
24
|
+
bindingId: string;
|
|
25
|
+
created: string;
|
|
26
|
+
instance: AppDatabaseStatusInstance | null;
|
|
27
|
+
integration: AppDatabaseStatusIntegration | null;
|
|
28
|
+
lastActivity: string;
|
|
29
|
+
lifecycleState: LifecycleState;
|
|
30
|
+
logicalName: string;
|
|
31
|
+
migrationDirectory: string | null;
|
|
32
|
+
migrationState: LifecycleMigrationState;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type AppDatabaseStatusTag = AppDatabaseTagRef & {
|
|
36
|
+
/** Null when this data tag has no live (non-cancelled) database. */
|
|
37
|
+
binding: AppDatabaseStatusBinding | null;
|
|
38
|
+
/**
|
|
39
|
+
* True when this data tag had a database that was retired. The live
|
|
40
|
+
* binding is omitted so the tag can be provisioned again, but Status
|
|
41
|
+
* still shows Deleted instead of No database.
|
|
42
|
+
*/
|
|
43
|
+
deleted: boolean;
|
|
44
|
+
displayName: string;
|
|
45
|
+
isDefaultEditor: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type AppDatabaseStatus = {
|
|
49
|
+
applicationId: string;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the app declares an App Database right now (working-state
|
|
52
|
+
* requirement). Same meaning as AppDatabaseCompatibility.hasAppDatabase.
|
|
53
|
+
* A leftover live binding with no requirement still appears under tags.
|
|
54
|
+
*/
|
|
55
|
+
hasAppDatabase: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Whether leftover App Database code is still in the working tree: SQL
|
|
58
|
+
* in the last known migration folder, or APIs still wired to the app's
|
|
59
|
+
* Postgres integration. Used to offer Clark cleanup after Edit databases
|
|
60
|
+
* are gone. Not a complete scan of every page or helper.
|
|
61
|
+
*/
|
|
62
|
+
hasDatabaseCode: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Whether the currently live published version stamped a database
|
|
65
|
+
* requirement. Working-state (Edit) rows are not this signal: a leftover
|
|
66
|
+
* production database stays undeletable until a version without databases
|
|
67
|
+
* is live.
|
|
68
|
+
*/
|
|
69
|
+
liveVersionHasAppDatabase: boolean;
|
|
70
|
+
tags: AppDatabaseStatusTag[];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Edit lifecycle membership: tags used for edit provisioning and migrations.
|
|
75
|
+
* That is every editor-only tag, plus the editor default even when that
|
|
76
|
+
* default is also deploy-allowed.
|
|
77
|
+
*/
|
|
78
|
+
export function isEditLifecycleTag(tag: Pick<AppDatabaseStatusTag, 'availableModes'>): boolean {
|
|
79
|
+
return tag.availableModes.includes(ViewMode.EDITOR);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Deploy lifecycle membership for builder-facing grouping: deploy-allowed
|
|
84
|
+
* tags that are not the editor default. Publish still may write the editor
|
|
85
|
+
* default when it is also deploy-allowed; that tag stays an Edit database.
|
|
86
|
+
*/
|
|
87
|
+
export function isDeployLifecycleTag(tag: Pick<AppDatabaseStatusTag, 'availableModes'>): boolean {
|
|
88
|
+
return tag.availableModes.includes(ViewMode.DEPLOYED) && !tag.availableModes.includes(ViewMode.EDITOR);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function isProductionDatabaseRetirementEligible(status: AppDatabaseStatus): boolean {
|
|
92
|
+
const hasEditDatabase = status.tags.some((tag) => tag.binding !== null && isEditLifecycleTag(tag));
|
|
93
|
+
const hasLeftoverProductionDatabase = status.tags.some((tag) => tag.binding !== null && !isEditLifecycleTag(tag));
|
|
94
|
+
return !hasEditDatabase && hasLeftoverProductionDatabase && !status.liveVersionHasAppDatabase;
|
|
95
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
mintDataImportConcurrencyKey,
|
|
5
|
+
parseDataImportMetadata,
|
|
6
|
+
parseDataImportTarget,
|
|
7
|
+
type DataImportUploadMetadata
|
|
8
|
+
} from './dataImport.js';
|
|
9
|
+
|
|
10
|
+
describe('data import contracts', () => {
|
|
11
|
+
test('validates destination descriptors without fixing the record to one destination schema', () => {
|
|
12
|
+
expect(
|
|
13
|
+
parseDataImportTarget({
|
|
14
|
+
applicationId: 'application-id',
|
|
15
|
+
bindingId: 'binding-id',
|
|
16
|
+
profile: 'production',
|
|
17
|
+
type: 'app_database'
|
|
18
|
+
})
|
|
19
|
+
).toEqual({
|
|
20
|
+
applicationId: 'application-id',
|
|
21
|
+
bindingId: 'binding-id',
|
|
22
|
+
profile: 'production',
|
|
23
|
+
type: 'app_database'
|
|
24
|
+
});
|
|
25
|
+
expect(parseDataImportTarget({ applicationId: 'application-id', type: 'app_database' })).toBeUndefined();
|
|
26
|
+
expect(parseDataImportTarget({ resourceId: 'resource-id', type: 'unknown' })).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('keeps signed URLs and part ETags out of durable upload metadata', () => {
|
|
30
|
+
const metadata: DataImportUploadMetadata = {
|
|
31
|
+
completedPartNumbers: [1, 2],
|
|
32
|
+
filename: 'data.csv',
|
|
33
|
+
objectKey: 'imports/profile/resource/import/data.csv',
|
|
34
|
+
partCount: 2,
|
|
35
|
+
partSizeBytes: 16 * 1024 * 1024,
|
|
36
|
+
uploadId: 'upload-id'
|
|
37
|
+
};
|
|
38
|
+
expect(metadata.completedPartNumbers).toEqual([1, 2]);
|
|
39
|
+
|
|
40
|
+
// @ts-expect-error Signed URLs are callback-only.
|
|
41
|
+
const withSignedUrl: DataImportUploadMetadata = { filename: 'data.csv', uploadUrl: 'https://uploads.example/object' };
|
|
42
|
+
// @ts-expect-error Part ETags are sent for completion, not persisted.
|
|
43
|
+
const withEtags: DataImportUploadMetadata = { eTags: ['etag'], filename: 'data.csv' };
|
|
44
|
+
expect(withSignedUrl).toBeDefined();
|
|
45
|
+
expect(withEtags).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('stores one csv table spelling so equivalent names share an active key', () => {
|
|
49
|
+
const target = {
|
|
50
|
+
applicationId: 'application-id',
|
|
51
|
+
bindingId: 'binding-id',
|
|
52
|
+
profile: 'production',
|
|
53
|
+
type: 'app_database' as const
|
|
54
|
+
};
|
|
55
|
+
const spaced = parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: ' public.orders ' });
|
|
56
|
+
const plain = parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: 'public.orders' });
|
|
57
|
+
|
|
58
|
+
expect(spaced).toEqual({ kind: 'csv', mode: 'append', table: 'public.orders' });
|
|
59
|
+
expect(plain).toEqual(spaced);
|
|
60
|
+
if (!spaced || !plain) {
|
|
61
|
+
throw new Error('expected both table spellings to parse');
|
|
62
|
+
}
|
|
63
|
+
expect(mintDataImportConcurrencyKey(spaced, target)).toBe(`csv:${target.bindingId}:public.orders`);
|
|
64
|
+
expect(mintDataImportConcurrencyKey(plain, target)).toBe(mintDataImportConcurrencyKey(spaced, target));
|
|
65
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: ' ' })).toBeUndefined();
|
|
66
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: 'public. order items' })).toBeUndefined();
|
|
67
|
+
for (const table of ['PUBLIC.ORDERS', 'public."orders"', '"public"."orders"', 'orders']) {
|
|
68
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table })).toBeUndefined();
|
|
69
|
+
}
|
|
70
|
+
const sixtyThree = 'a'.repeat(63);
|
|
71
|
+
const sixtyFour = 'a'.repeat(64);
|
|
72
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: `public.${sixtyThree}` })).toEqual({
|
|
73
|
+
kind: 'csv',
|
|
74
|
+
mode: 'append',
|
|
75
|
+
table: `public.${sixtyThree}`
|
|
76
|
+
});
|
|
77
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: `public.${sixtyFour}` })).toBeUndefined();
|
|
78
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: `${sixtyFour}.orders` })).toBeUndefined();
|
|
79
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: `public.${sixtyThree}x` })).toBeUndefined();
|
|
80
|
+
expect(parseDataImportMetadata('csv', { kind: 'csv', mode: 'append', table: `public.${sixtyThree}y` })).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('rejects zero prefix sizes and a zero postgres major', () => {
|
|
84
|
+
const base = { kind: 'pg_dump_custom' as const };
|
|
85
|
+
expect(parseDataImportMetadata('pg_dump_custom', { ...base, currentPrefixSizeBytes: 0 })).toBeUndefined();
|
|
86
|
+
expect(parseDataImportMetadata('pg_dump_custom', { ...base, requestedPrefixSizeBytes: 0 })).toBeUndefined();
|
|
87
|
+
expect(parseDataImportMetadata('pg_dump_custom', { ...base, postgresMajorVersion: 0 })).toBeUndefined();
|
|
88
|
+
expect(parseDataImportMetadata('pg_dump_custom', { ...base, postgresMajorVersion: -1 })).toBeUndefined();
|
|
89
|
+
expect(parseDataImportMetadata('pg_dump_custom', { ...base, currentPrefixSizeBytes: 1, postgresMajorVersion: 16 })).toEqual({
|
|
90
|
+
kind: 'pg_dump_custom',
|
|
91
|
+
currentPrefixSizeBytes: 1,
|
|
92
|
+
postgresMajorVersion: 16
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|