neonctl 2.21.2 → 2.22.2
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/README.md +158 -16
- package/commands/checkout.js +249 -0
- package/commands/checkout.test.js +170 -0
- package/commands/connection_string.js +6 -1
- package/commands/data_api.js +286 -0
- package/commands/data_api.test.js +169 -0
- package/commands/index.js +8 -0
- package/commands/link.js +667 -0
- package/commands/link.test.js +381 -0
- package/commands/psql.js +57 -0
- package/commands/psql.test.js +49 -0
- package/commands/set_context.js +7 -2
- package/context.js +86 -14
- package/context.test.js +119 -0
- package/index.js +3 -0
- package/package.json +48 -52
- package/utils/enrichers.js +18 -1
- package/utils/middlewares.js +1 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { isAxiosError } from 'axios';
|
|
2
|
+
import { retryOnLock } from '../api.js';
|
|
3
|
+
import { branchIdFromProps, fillSingleProject, resolveSingleDatabase, } from '../utils/enrichers.js';
|
|
4
|
+
import { log } from '../log.js';
|
|
5
|
+
import { writer } from '../writer.js';
|
|
6
|
+
const SETTINGS_FIELDS = [
|
|
7
|
+
'db_aggregates_enabled',
|
|
8
|
+
'db_anon_role',
|
|
9
|
+
'db_extra_search_path',
|
|
10
|
+
'db_max_rows',
|
|
11
|
+
'db_schemas',
|
|
12
|
+
'jwt_role_claim_key',
|
|
13
|
+
'jwt_cache_max_lifetime',
|
|
14
|
+
'openapi_mode',
|
|
15
|
+
'server_cors_allowed_origins',
|
|
16
|
+
'server_timing_enabled',
|
|
17
|
+
];
|
|
18
|
+
const settingsFlags = {
|
|
19
|
+
'db-aggregates-enabled': {
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
describe: 'Enable aggregate functions in queries',
|
|
22
|
+
},
|
|
23
|
+
'db-anon-role': {
|
|
24
|
+
type: 'string',
|
|
25
|
+
describe: 'Database role used for anonymous (unauthenticated) requests',
|
|
26
|
+
},
|
|
27
|
+
'db-extra-search-path': {
|
|
28
|
+
type: 'string',
|
|
29
|
+
describe: 'Extra schemas appended to the search path',
|
|
30
|
+
},
|
|
31
|
+
'db-max-rows': {
|
|
32
|
+
type: 'number',
|
|
33
|
+
describe: 'Maximum number of rows returned by a single request',
|
|
34
|
+
},
|
|
35
|
+
'db-schemas': {
|
|
36
|
+
type: 'string',
|
|
37
|
+
describe: 'Comma-separated list of schemas exposed via the Data API',
|
|
38
|
+
},
|
|
39
|
+
'jwt-role-claim-key': {
|
|
40
|
+
type: 'string',
|
|
41
|
+
describe: 'JWT claim path used to extract the role',
|
|
42
|
+
},
|
|
43
|
+
'jwt-cache-max-lifetime': {
|
|
44
|
+
type: 'number',
|
|
45
|
+
describe: 'Maximum JWT cache lifetime in seconds',
|
|
46
|
+
},
|
|
47
|
+
'openapi-mode': {
|
|
48
|
+
type: 'string',
|
|
49
|
+
choices: ['ignore-privileges', 'disabled'],
|
|
50
|
+
describe: 'OpenAPI mode',
|
|
51
|
+
},
|
|
52
|
+
'server-cors-allowed-origins': {
|
|
53
|
+
type: 'string',
|
|
54
|
+
describe: 'CORS allowed origins',
|
|
55
|
+
},
|
|
56
|
+
'server-timing-enabled': {
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
describe: 'Enable Server-Timing response headers',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
export const command = 'data-api';
|
|
62
|
+
export const describe = 'Manage the Neon Data API for a database';
|
|
63
|
+
export const builder = (argv) => argv
|
|
64
|
+
.usage('$0 data-api <sub-command> [options]')
|
|
65
|
+
.options({
|
|
66
|
+
'project-id': {
|
|
67
|
+
describe: 'Project ID',
|
|
68
|
+
type: 'string',
|
|
69
|
+
},
|
|
70
|
+
branch: {
|
|
71
|
+
describe: 'Branch ID or name',
|
|
72
|
+
type: 'string',
|
|
73
|
+
},
|
|
74
|
+
database: {
|
|
75
|
+
describe: 'Database name',
|
|
76
|
+
type: 'string',
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
.middleware(fillSingleProject)
|
|
80
|
+
.command('create', 'Provision the Neon Data API for a database', (yargs) => yargs.options({
|
|
81
|
+
'auth-provider': {
|
|
82
|
+
type: 'string',
|
|
83
|
+
choices: ['neon_auth', 'external'],
|
|
84
|
+
describe: 'Authentication provider',
|
|
85
|
+
},
|
|
86
|
+
'jwks-url': {
|
|
87
|
+
type: 'string',
|
|
88
|
+
describe: 'URL that lists the JWKS (used with external auth)',
|
|
89
|
+
},
|
|
90
|
+
'provider-name': {
|
|
91
|
+
type: 'string',
|
|
92
|
+
describe: 'Name of the auth provider (e.g. Clerk, Stytch, Auth0)',
|
|
93
|
+
},
|
|
94
|
+
'jwt-audience': {
|
|
95
|
+
type: 'string',
|
|
96
|
+
describe: 'Expected JWT audience claim',
|
|
97
|
+
},
|
|
98
|
+
'add-default-grants': {
|
|
99
|
+
type: 'boolean',
|
|
100
|
+
describe: 'Grant all permissions on tables in the public schema to authenticated users',
|
|
101
|
+
},
|
|
102
|
+
'skip-auth-schema': {
|
|
103
|
+
type: 'boolean',
|
|
104
|
+
describe: 'Skip creating the auth schema and RLS functions',
|
|
105
|
+
},
|
|
106
|
+
...settingsFlags,
|
|
107
|
+
}), (args) => create(args))
|
|
108
|
+
.command('get', 'Show the Neon Data API status and settings', (yargs) => yargs, (args) => get(args))
|
|
109
|
+
.command('update', 'Update Neon Data API settings (merges with current settings by default)', (yargs) => yargs.options({
|
|
110
|
+
replace: {
|
|
111
|
+
type: 'boolean',
|
|
112
|
+
default: false,
|
|
113
|
+
describe: 'Replace settings with only the flags provided. Omitted settings revert to server defaults.',
|
|
114
|
+
},
|
|
115
|
+
...settingsFlags,
|
|
116
|
+
}), (args) => update(args))
|
|
117
|
+
.command('refresh-schema', 'Refresh the Data API schema cache without changing settings', (yargs) => yargs, (args) => refreshSchema(args))
|
|
118
|
+
.command('delete', 'Tear down the Neon Data API for a database', (yargs) => yargs, (args) => deleteDataApi(args));
|
|
119
|
+
export const handler = (args) => {
|
|
120
|
+
return args;
|
|
121
|
+
};
|
|
122
|
+
const TOP_LEVEL_CREATE_FIELDS = [
|
|
123
|
+
'auth_provider',
|
|
124
|
+
'jwks_url',
|
|
125
|
+
'provider_name',
|
|
126
|
+
'jwt_audience',
|
|
127
|
+
'add_default_grants',
|
|
128
|
+
'skip_auth_schema',
|
|
129
|
+
];
|
|
130
|
+
const argKey = (snake) => snake.replace(/_/g, '-');
|
|
131
|
+
const buildSettings = (args) => {
|
|
132
|
+
const settings = {};
|
|
133
|
+
for (const field of SETTINGS_FIELDS) {
|
|
134
|
+
const value = args[argKey(field)];
|
|
135
|
+
if (value === undefined)
|
|
136
|
+
continue;
|
|
137
|
+
if (field === 'db_schemas' && typeof value === 'string') {
|
|
138
|
+
settings[field] = value
|
|
139
|
+
.split(',')
|
|
140
|
+
.map((s) => s.trim())
|
|
141
|
+
.filter(Boolean);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
settings[field] = value;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return Object.keys(settings).length > 0
|
|
148
|
+
? settings
|
|
149
|
+
: undefined;
|
|
150
|
+
};
|
|
151
|
+
const buildCreateBody = (args) => {
|
|
152
|
+
const body = {};
|
|
153
|
+
for (const field of TOP_LEVEL_CREATE_FIELDS) {
|
|
154
|
+
const value = args[argKey(field)];
|
|
155
|
+
if (value !== undefined)
|
|
156
|
+
body[field] = value;
|
|
157
|
+
}
|
|
158
|
+
const settings = buildSettings(args);
|
|
159
|
+
if (settings)
|
|
160
|
+
body.settings = settings;
|
|
161
|
+
return body;
|
|
162
|
+
};
|
|
163
|
+
const create = async (props) => {
|
|
164
|
+
const branchId = await branchIdFromProps(props);
|
|
165
|
+
const database = await resolveSingleDatabase({
|
|
166
|
+
apiClient: props.apiClient,
|
|
167
|
+
projectId: props.projectId,
|
|
168
|
+
branchId,
|
|
169
|
+
database: props.database,
|
|
170
|
+
});
|
|
171
|
+
const body = buildCreateBody(props);
|
|
172
|
+
const { data } = await retryOnLock(() => props.apiClient.createProjectBranchDataApi(props.projectId, branchId, database, body));
|
|
173
|
+
writer(props).end(data, { fields: ['url'] });
|
|
174
|
+
};
|
|
175
|
+
const GET_FIELDS = ['url', 'status', 'db_schemas'];
|
|
176
|
+
const get = async (props) => {
|
|
177
|
+
const branchId = await branchIdFromProps(props);
|
|
178
|
+
const database = await resolveSingleDatabase({
|
|
179
|
+
apiClient: props.apiClient,
|
|
180
|
+
projectId: props.projectId,
|
|
181
|
+
branchId,
|
|
182
|
+
database: props.database,
|
|
183
|
+
});
|
|
184
|
+
const { data } = await props.apiClient.getProjectBranchDataApi(props.projectId, branchId, database);
|
|
185
|
+
// Drop available_schemas from json/yaml output (not part of the public surface).
|
|
186
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
187
|
+
const { available_schemas: _ignored, ...publicData } = data;
|
|
188
|
+
// For table output, flatten db_schemas onto the top-level for column rendering.
|
|
189
|
+
const tableRow = {
|
|
190
|
+
url: publicData.url,
|
|
191
|
+
status: publicData.status,
|
|
192
|
+
db_schemas: (publicData.settings?.db_schemas ?? []).join(', '),
|
|
193
|
+
};
|
|
194
|
+
if (props.output === 'json' || props.output === 'yaml') {
|
|
195
|
+
writer(props).end(publicData, {
|
|
196
|
+
fields: ['url', 'status', 'settings'],
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
writer(props).end(tableRow, { fields: GET_FIELDS });
|
|
201
|
+
};
|
|
202
|
+
const update = async (props) => {
|
|
203
|
+
const branchId = await branchIdFromProps(props);
|
|
204
|
+
const database = await resolveSingleDatabase({
|
|
205
|
+
apiClient: props.apiClient,
|
|
206
|
+
projectId: props.projectId,
|
|
207
|
+
branchId,
|
|
208
|
+
database: props.database,
|
|
209
|
+
});
|
|
210
|
+
const userSettings = buildSettings(props);
|
|
211
|
+
if (!userSettings) {
|
|
212
|
+
throw new Error('No settings flags provided. Pass at least one setting flag to update, or use `data-api refresh-schema` to refresh the schema cache without changing settings.');
|
|
213
|
+
}
|
|
214
|
+
let settings;
|
|
215
|
+
if (props.replace) {
|
|
216
|
+
settings = userSettings;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
let current;
|
|
220
|
+
try {
|
|
221
|
+
const { data } = await props.apiClient.getProjectBranchDataApi(props.projectId, branchId, database);
|
|
222
|
+
current = data.settings ?? undefined;
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
if (isAxiosError(err) && err.response?.status === 404) {
|
|
226
|
+
throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}. Run \`neonctl data-api create\` first.`);
|
|
227
|
+
}
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
230
|
+
if (!current) {
|
|
231
|
+
throw new Error(`Could not read current Data API settings for ${database} on branch ${branchId}. Retry, or pass --replace to overwrite.`);
|
|
232
|
+
}
|
|
233
|
+
settings = { ...current, ...(userSettings ?? {}) };
|
|
234
|
+
}
|
|
235
|
+
const body = {};
|
|
236
|
+
if (settings)
|
|
237
|
+
body.settings = settings;
|
|
238
|
+
try {
|
|
239
|
+
await retryOnLock(() => props.apiClient.updateProjectBranchDataApi(props.projectId, branchId, database, body));
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
if (isAxiosError(err) && err.response?.status === 404) {
|
|
243
|
+
throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}. Run \`neonctl data-api create\` first.`);
|
|
244
|
+
}
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
log.info(`Data API settings updated for ${database} on branch ${branchId}`);
|
|
248
|
+
};
|
|
249
|
+
const refreshSchema = async (props) => {
|
|
250
|
+
const branchId = await branchIdFromProps(props);
|
|
251
|
+
const database = await resolveSingleDatabase({
|
|
252
|
+
apiClient: props.apiClient,
|
|
253
|
+
projectId: props.projectId,
|
|
254
|
+
branchId,
|
|
255
|
+
database: props.database,
|
|
256
|
+
});
|
|
257
|
+
try {
|
|
258
|
+
await retryOnLock(() => props.apiClient.updateProjectBranchDataApi(props.projectId, branchId, database, {}));
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
if (isAxiosError(err) && err.response?.status === 404) {
|
|
262
|
+
throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}. Run \`neonctl data-api create\` first.`);
|
|
263
|
+
}
|
|
264
|
+
throw err;
|
|
265
|
+
}
|
|
266
|
+
log.info(`Data API schema cache refreshed for ${database} on branch ${branchId}`);
|
|
267
|
+
};
|
|
268
|
+
const deleteDataApi = async (props) => {
|
|
269
|
+
const branchId = await branchIdFromProps(props);
|
|
270
|
+
const database = await resolveSingleDatabase({
|
|
271
|
+
apiClient: props.apiClient,
|
|
272
|
+
projectId: props.projectId,
|
|
273
|
+
branchId,
|
|
274
|
+
database: props.database,
|
|
275
|
+
});
|
|
276
|
+
try {
|
|
277
|
+
await retryOnLock(() => props.apiClient.deleteProjectBranchDataApi(props.projectId, branchId, database));
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
if (isAxiosError(err) && err.response?.status === 404) {
|
|
281
|
+
throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}.`);
|
|
282
|
+
}
|
|
283
|
+
throw err;
|
|
284
|
+
}
|
|
285
|
+
log.info(`Data API deleted for ${database} on branch ${branchId}`);
|
|
286
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { describe } from 'vitest';
|
|
2
|
+
import { test } from '../test_utils/fixtures';
|
|
3
|
+
describe('data-api', () => {
|
|
4
|
+
test('delete', async ({ testCliCommand }) => {
|
|
5
|
+
await testCliCommand([
|
|
6
|
+
'data-api',
|
|
7
|
+
'delete',
|
|
8
|
+
'--project-id',
|
|
9
|
+
'test-project-123456',
|
|
10
|
+
'--branch',
|
|
11
|
+
'main',
|
|
12
|
+
'--database',
|
|
13
|
+
'db1',
|
|
14
|
+
], {
|
|
15
|
+
mockDir: 'single_org',
|
|
16
|
+
stderr: 'INFO: Data API deleted for db1 on branch br-main-branch-123456',
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
test('get', async ({ testCliCommand }) => {
|
|
20
|
+
await testCliCommand([
|
|
21
|
+
'data-api',
|
|
22
|
+
'get',
|
|
23
|
+
'--project-id',
|
|
24
|
+
'test-project-123456',
|
|
25
|
+
'--branch',
|
|
26
|
+
'main',
|
|
27
|
+
'--database',
|
|
28
|
+
'db1',
|
|
29
|
+
], { mockDir: 'single_org' });
|
|
30
|
+
});
|
|
31
|
+
test('get with table output', async ({ testCliCommand }) => {
|
|
32
|
+
await testCliCommand([
|
|
33
|
+
'data-api',
|
|
34
|
+
'get',
|
|
35
|
+
'--project-id',
|
|
36
|
+
'test-project-123456',
|
|
37
|
+
'--branch',
|
|
38
|
+
'main',
|
|
39
|
+
'--database',
|
|
40
|
+
'db1',
|
|
41
|
+
], { mockDir: 'single_org', outputTable: true });
|
|
42
|
+
});
|
|
43
|
+
test('create', async ({ testCliCommand }) => {
|
|
44
|
+
await testCliCommand([
|
|
45
|
+
'data-api',
|
|
46
|
+
'create',
|
|
47
|
+
'--project-id',
|
|
48
|
+
'test-project-123456',
|
|
49
|
+
'--branch',
|
|
50
|
+
'main',
|
|
51
|
+
'--database',
|
|
52
|
+
'db1',
|
|
53
|
+
'--auth-provider',
|
|
54
|
+
'neon_auth',
|
|
55
|
+
'--add-default-grants',
|
|
56
|
+
'--db-schemas',
|
|
57
|
+
'public,analytics',
|
|
58
|
+
'--db-max-rows',
|
|
59
|
+
'500',
|
|
60
|
+
], { mockDir: 'single_org' });
|
|
61
|
+
});
|
|
62
|
+
test('update --replace', async ({ testCliCommand }) => {
|
|
63
|
+
await testCliCommand([
|
|
64
|
+
'data-api',
|
|
65
|
+
'update',
|
|
66
|
+
'--project-id',
|
|
67
|
+
'test-project-123456',
|
|
68
|
+
'--branch',
|
|
69
|
+
'main',
|
|
70
|
+
'--database',
|
|
71
|
+
'db1',
|
|
72
|
+
'--replace',
|
|
73
|
+
'--db-max-rows',
|
|
74
|
+
'250',
|
|
75
|
+
], {
|
|
76
|
+
mockDir: 'single_org',
|
|
77
|
+
stderr: 'INFO: Data API settings updated for db1 on branch br-main-branch-123456',
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
test('update merges with existing settings', async ({ testCliCommand }) => {
|
|
81
|
+
await testCliCommand([
|
|
82
|
+
'data-api',
|
|
83
|
+
'update',
|
|
84
|
+
'--project-id',
|
|
85
|
+
'test-project-123456',
|
|
86
|
+
'--branch',
|
|
87
|
+
'main',
|
|
88
|
+
'--database',
|
|
89
|
+
'db1',
|
|
90
|
+
'--db-max-rows',
|
|
91
|
+
'9999',
|
|
92
|
+
], {
|
|
93
|
+
mockDir: 'single_org',
|
|
94
|
+
stderr: 'INFO: Data API settings updated for db1 on branch br-main-branch-123456',
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
test('update fails when no settings flags are passed', async ({ testCliCommand, }) => {
|
|
98
|
+
await testCliCommand([
|
|
99
|
+
'data-api',
|
|
100
|
+
'update',
|
|
101
|
+
'--project-id',
|
|
102
|
+
'test-project-123456',
|
|
103
|
+
'--branch',
|
|
104
|
+
'main',
|
|
105
|
+
'--database',
|
|
106
|
+
'db1',
|
|
107
|
+
'--replace',
|
|
108
|
+
], {
|
|
109
|
+
mockDir: 'single_org',
|
|
110
|
+
code: 1,
|
|
111
|
+
stderr: 'ERROR: No settings flags provided. Pass at least one setting flag to update, or use `data-api refresh-schema` to refresh the schema cache without changing settings.',
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
test('refresh-schema sends empty PATCH', async ({ testCliCommand }) => {
|
|
115
|
+
await testCliCommand([
|
|
116
|
+
'data-api',
|
|
117
|
+
'refresh-schema',
|
|
118
|
+
'--project-id',
|
|
119
|
+
'test-project-123456',
|
|
120
|
+
'--branch',
|
|
121
|
+
'main',
|
|
122
|
+
'--database',
|
|
123
|
+
'db1',
|
|
124
|
+
], {
|
|
125
|
+
mockDir: 'single_org',
|
|
126
|
+
stderr: 'INFO: Data API schema cache refreshed for db1 on branch br-main-branch-123456',
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
test('delete with implicit database', async ({ testCliCommand }) => {
|
|
130
|
+
// The single_org fixture has exactly one database (db1) on the main branch,
|
|
131
|
+
// so omitting --database should resolve it automatically.
|
|
132
|
+
await testCliCommand([
|
|
133
|
+
'data-api',
|
|
134
|
+
'delete',
|
|
135
|
+
'--project-id',
|
|
136
|
+
'test-project-123456',
|
|
137
|
+
'--branch',
|
|
138
|
+
'main',
|
|
139
|
+
], {
|
|
140
|
+
mockDir: 'single_org',
|
|
141
|
+
stderr: 'INFO: Data API deleted for db1 on branch br-main-branch-123456',
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
test('get with fully implicit resolution', async ({ testCliCommand }) => {
|
|
145
|
+
// single_org has one org, one project, one default branch (main), one
|
|
146
|
+
// database (db1) — all three should be auto-resolved when omitted.
|
|
147
|
+
await testCliCommand(['data-api', 'get'], { mockDir: 'single_org' });
|
|
148
|
+
});
|
|
149
|
+
test('update with fully implicit resolution', async ({ testCliCommand }) => {
|
|
150
|
+
await testCliCommand(['data-api', 'update', '--db-max-rows', '9999'], {
|
|
151
|
+
mockDir: 'single_org',
|
|
152
|
+
stderr: 'INFO: Data API settings updated for db1 on branch br-main-branch-123456',
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
test('delete fails with helpful error when multiple databases', async ({ testCliCommand, }) => {
|
|
156
|
+
await testCliCommand([
|
|
157
|
+
'data-api',
|
|
158
|
+
'delete',
|
|
159
|
+
'--project-id',
|
|
160
|
+
'test-project-123456',
|
|
161
|
+
'--branch',
|
|
162
|
+
'main',
|
|
163
|
+
], {
|
|
164
|
+
mockDir: 'single_org_multidb',
|
|
165
|
+
code: 1,
|
|
166
|
+
stderr: 'ERROR: Multiple databases found for the branch, please provide one with the --database option: db1, db2',
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
package/commands/index.js
CHANGED
|
@@ -9,8 +9,12 @@ import * as databases from './databases.js';
|
|
|
9
9
|
import * as roles from './roles.js';
|
|
10
10
|
import * as operations from './operations.js';
|
|
11
11
|
import * as cs from './connection_string.js';
|
|
12
|
+
import * as psql from './psql.js';
|
|
12
13
|
import * as setContext from './set_context.js';
|
|
14
|
+
import * as checkout from './checkout.js';
|
|
15
|
+
import * as link from './link.js';
|
|
13
16
|
import * as init from './init.js';
|
|
17
|
+
import * as dataApi from './data_api.js';
|
|
14
18
|
export default [
|
|
15
19
|
auth,
|
|
16
20
|
users,
|
|
@@ -23,6 +27,10 @@ export default [
|
|
|
23
27
|
roles,
|
|
24
28
|
operations,
|
|
25
29
|
cs,
|
|
30
|
+
psql,
|
|
26
31
|
setContext,
|
|
32
|
+
checkout,
|
|
33
|
+
link,
|
|
27
34
|
init,
|
|
35
|
+
dataApi,
|
|
28
36
|
];
|