s3db.js 11.3.1 → 12.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/README.md +102 -8
- package/dist/s3db.cjs.js +36664 -15480
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.d.ts +57 -0
- package/dist/s3db.es.js +36661 -15531
- package/dist/s3db.es.js.map +1 -1
- package/mcp/entrypoint.js +58 -0
- package/mcp/tools/documentation.js +434 -0
- package/mcp/tools/index.js +4 -0
- package/package.json +27 -6
- package/src/behaviors/user-managed.js +13 -6
- package/src/client.class.js +41 -46
- package/src/concerns/base62.js +85 -0
- package/src/concerns/dictionary-encoding.js +294 -0
- package/src/concerns/geo-encoding.js +256 -0
- package/src/concerns/high-performance-inserter.js +34 -30
- package/src/concerns/ip.js +325 -0
- package/src/concerns/metadata-encoding.js +345 -66
- package/src/concerns/money.js +193 -0
- package/src/concerns/partition-queue.js +7 -4
- package/src/concerns/plugin-storage.js +39 -19
- package/src/database.class.js +76 -74
- package/src/errors.js +0 -4
- package/src/plugins/api/auth/api-key-auth.js +88 -0
- package/src/plugins/api/auth/basic-auth.js +154 -0
- package/src/plugins/api/auth/index.js +112 -0
- package/src/plugins/api/auth/jwt-auth.js +169 -0
- package/src/plugins/api/index.js +539 -0
- package/src/plugins/api/middlewares/index.js +15 -0
- package/src/plugins/api/middlewares/validator.js +185 -0
- package/src/plugins/api/routes/auth-routes.js +241 -0
- package/src/plugins/api/routes/resource-routes.js +304 -0
- package/src/plugins/api/server.js +350 -0
- package/src/plugins/api/utils/error-handler.js +147 -0
- package/src/plugins/api/utils/openapi-generator.js +1240 -0
- package/src/plugins/api/utils/response-formatter.js +218 -0
- package/src/plugins/backup/streaming-exporter.js +132 -0
- package/src/plugins/backup.plugin.js +103 -50
- package/src/plugins/cache/s3-cache.class.js +95 -47
- package/src/plugins/cache.plugin.js +107 -9
- package/src/plugins/concerns/plugin-dependencies.js +313 -0
- package/src/plugins/concerns/prometheus-formatter.js +255 -0
- package/src/plugins/consumers/rabbitmq-consumer.js +4 -0
- package/src/plugins/consumers/sqs-consumer.js +4 -0
- package/src/plugins/costs.plugin.js +255 -39
- package/src/plugins/eventual-consistency/helpers.js +15 -1
- package/src/plugins/geo.plugin.js +873 -0
- package/src/plugins/importer/index.js +1020 -0
- package/src/plugins/index.js +11 -0
- package/src/plugins/metrics.plugin.js +163 -4
- package/src/plugins/queue-consumer.plugin.js +6 -27
- package/src/plugins/relation.errors.js +139 -0
- package/src/plugins/relation.plugin.js +1242 -0
- package/src/plugins/replicators/bigquery-replicator.class.js +180 -8
- package/src/plugins/replicators/dynamodb-replicator.class.js +383 -0
- package/src/plugins/replicators/index.js +28 -3
- package/src/plugins/replicators/mongodb-replicator.class.js +391 -0
- package/src/plugins/replicators/mysql-replicator.class.js +558 -0
- package/src/plugins/replicators/planetscale-replicator.class.js +409 -0
- package/src/plugins/replicators/postgres-replicator.class.js +182 -7
- package/src/plugins/replicators/s3db-replicator.class.js +1 -12
- package/src/plugins/replicators/schema-sync.helper.js +601 -0
- package/src/plugins/replicators/sqs-replicator.class.js +11 -9
- package/src/plugins/replicators/turso-replicator.class.js +416 -0
- package/src/plugins/replicators/webhook-replicator.class.js +612 -0
- package/src/plugins/state-machine.plugin.js +122 -68
- package/src/plugins/tfstate/README.md +745 -0
- package/src/plugins/tfstate/base-driver.js +80 -0
- package/src/plugins/tfstate/errors.js +112 -0
- package/src/plugins/tfstate/filesystem-driver.js +129 -0
- package/src/plugins/tfstate/index.js +2660 -0
- package/src/plugins/tfstate/s3-driver.js +192 -0
- package/src/plugins/ttl.plugin.js +536 -0
- package/src/resource.class.js +14 -10
- package/src/s3db.d.ts +57 -0
- package/src/schema.class.js +366 -32
- package/SECURITY.md +0 -76
- package/mcp/CLAUDE_CLI_SETUP.md +0 -302
- package/mcp/Dockerfile +0 -45
- package/mcp/Makefile +0 -162
- package/mcp/NPX_SETUP.md +0 -327
- package/mcp/PUBLISHING.md +0 -281
- package/mcp/README.md +0 -125
- package/mcp/docker-compose.yml +0 -120
- package/mcp/examples/test-filesystem-cache.js +0 -147
- package/mcp/examples/test-mcp.js +0 -433
- package/mcp/package.json +0 -66
- package/src/partition-drivers/base-partition-driver.js +0 -106
- package/src/partition-drivers/index.js +0 -66
- package/src/partition-drivers/memory-partition-driver.js +0 -289
- package/src/partition-drivers/sqs-partition-driver.js +0 -337
- package/src/partition-drivers/sync-partition-driver.js +0 -38
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Middleware - Schema validation for resource operations
|
|
3
|
+
*
|
|
4
|
+
* Uses s3db.js resource schemas to validate request data
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { validationError } from '../utils/response-formatter.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create validation middleware for a resource
|
|
11
|
+
* @param {Object} resource - s3db.js Resource instance
|
|
12
|
+
* @param {Object} options - Validation options
|
|
13
|
+
* @param {boolean} options.validateOnInsert - Validate on POST (default: true)
|
|
14
|
+
* @param {boolean} options.validateOnUpdate - Validate on PUT/PATCH (default: true)
|
|
15
|
+
* @param {boolean} options.partial - Allow partial validation for PATCH (default: true)
|
|
16
|
+
* @returns {Function} Hono middleware
|
|
17
|
+
*/
|
|
18
|
+
export function createValidationMiddleware(resource, options = {}) {
|
|
19
|
+
const {
|
|
20
|
+
validateOnInsert = true,
|
|
21
|
+
validateOnUpdate = true,
|
|
22
|
+
partial = true
|
|
23
|
+
} = options;
|
|
24
|
+
|
|
25
|
+
const schema = resource.schema;
|
|
26
|
+
|
|
27
|
+
return async (c, next) => {
|
|
28
|
+
const method = c.req.method;
|
|
29
|
+
const shouldValidate =
|
|
30
|
+
(method === 'POST' && validateOnInsert) ||
|
|
31
|
+
((method === 'PUT' || method === 'PATCH') && validateOnUpdate);
|
|
32
|
+
|
|
33
|
+
if (!shouldValidate) {
|
|
34
|
+
return await next();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Get request body
|
|
38
|
+
let data;
|
|
39
|
+
try {
|
|
40
|
+
data = await c.req.json();
|
|
41
|
+
} catch (err) {
|
|
42
|
+
const response = validationError([
|
|
43
|
+
{ field: 'body', message: 'Invalid JSON in request body' }
|
|
44
|
+
]);
|
|
45
|
+
return c.json(response, response._status);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// For PATCH, allow partial data
|
|
49
|
+
const isPartial = method === 'PATCH' && partial;
|
|
50
|
+
|
|
51
|
+
// Validate using resource schema
|
|
52
|
+
const validationResult = schema.validate(data, {
|
|
53
|
+
partial: isPartial,
|
|
54
|
+
strict: !isPartial
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!validationResult.valid) {
|
|
58
|
+
const errors = validationResult.errors.map(err => ({
|
|
59
|
+
field: err.field || err.attribute || 'unknown',
|
|
60
|
+
message: err.message,
|
|
61
|
+
expected: err.expected,
|
|
62
|
+
actual: err.actual
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
const response = validationError(errors);
|
|
66
|
+
return c.json(response, response._status);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Store validated data in context (optional)
|
|
70
|
+
c.set('validatedData', validationResult.data || data);
|
|
71
|
+
|
|
72
|
+
await next();
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create validation middleware that validates query parameters
|
|
78
|
+
* @param {Object} schema - Validation schema for query params
|
|
79
|
+
* @returns {Function} Hono middleware
|
|
80
|
+
*/
|
|
81
|
+
export function createQueryValidation(schema = {}) {
|
|
82
|
+
return async (c, next) => {
|
|
83
|
+
const query = c.req.query();
|
|
84
|
+
const errors = [];
|
|
85
|
+
|
|
86
|
+
// Validate each query parameter
|
|
87
|
+
for (const [key, rules] of Object.entries(schema)) {
|
|
88
|
+
const value = query[key];
|
|
89
|
+
|
|
90
|
+
// Check required
|
|
91
|
+
if (rules.required && !value) {
|
|
92
|
+
errors.push({
|
|
93
|
+
field: key,
|
|
94
|
+
message: `Query parameter '${key}' is required`
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!value) continue;
|
|
100
|
+
|
|
101
|
+
// Check type
|
|
102
|
+
if (rules.type) {
|
|
103
|
+
if (rules.type === 'number' && isNaN(Number(value))) {
|
|
104
|
+
errors.push({
|
|
105
|
+
field: key,
|
|
106
|
+
message: `Query parameter '${key}' must be a number`,
|
|
107
|
+
actual: value
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (rules.type === 'boolean' && !['true', 'false', '1', '0'].includes(value.toLowerCase())) {
|
|
112
|
+
errors.push({
|
|
113
|
+
field: key,
|
|
114
|
+
message: `Query parameter '${key}' must be a boolean`,
|
|
115
|
+
actual: value
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check min/max for numbers
|
|
121
|
+
if (rules.type === 'number') {
|
|
122
|
+
const num = Number(value);
|
|
123
|
+
|
|
124
|
+
if (rules.min !== undefined && num < rules.min) {
|
|
125
|
+
errors.push({
|
|
126
|
+
field: key,
|
|
127
|
+
message: `Query parameter '${key}' must be at least ${rules.min}`,
|
|
128
|
+
actual: num
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (rules.max !== undefined && num > rules.max) {
|
|
133
|
+
errors.push({
|
|
134
|
+
field: key,
|
|
135
|
+
message: `Query parameter '${key}' must be at most ${rules.max}`,
|
|
136
|
+
actual: num
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check enum values
|
|
142
|
+
if (rules.enum && !rules.enum.includes(value)) {
|
|
143
|
+
errors.push({
|
|
144
|
+
field: key,
|
|
145
|
+
message: `Query parameter '${key}' must be one of: ${rules.enum.join(', ')}`,
|
|
146
|
+
actual: value
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (errors.length > 0) {
|
|
152
|
+
const response = validationError(errors);
|
|
153
|
+
return c.json(response, response._status);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
await next();
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Standard query parameters validation for list endpoints
|
|
162
|
+
*/
|
|
163
|
+
export const listQueryValidation = createQueryValidation({
|
|
164
|
+
limit: {
|
|
165
|
+
type: 'number',
|
|
166
|
+
min: 1,
|
|
167
|
+
max: 1000
|
|
168
|
+
},
|
|
169
|
+
offset: {
|
|
170
|
+
type: 'number',
|
|
171
|
+
min: 0
|
|
172
|
+
},
|
|
173
|
+
partition: {
|
|
174
|
+
type: 'string'
|
|
175
|
+
},
|
|
176
|
+
partitionValues: {
|
|
177
|
+
type: 'string' // JSON string
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
export default {
|
|
182
|
+
createValidationMiddleware,
|
|
183
|
+
createQueryValidation,
|
|
184
|
+
listQueryValidation
|
|
185
|
+
};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Routes - Login, register, and token management endpoints
|
|
3
|
+
*
|
|
4
|
+
* Provides user authentication endpoints for the API
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Hono } from 'hono';
|
|
8
|
+
import { asyncHandler } from '../utils/error-handler.js';
|
|
9
|
+
import * as formatter from '../utils/response-formatter.js';
|
|
10
|
+
import { createToken } from '../auth/jwt-auth.js';
|
|
11
|
+
import { generateApiKey } from '../auth/api-key-auth.js';
|
|
12
|
+
import { encrypt } from '../../../concerns/crypto.js';
|
|
13
|
+
import tryFn from '../../../concerns/try-fn.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create authentication routes
|
|
17
|
+
* @param {Object} usersResource - s3db.js users resource
|
|
18
|
+
* @param {Object} config - Auth configuration
|
|
19
|
+
* @returns {Hono} Hono app with auth routes
|
|
20
|
+
*/
|
|
21
|
+
export function createAuthRoutes(usersResource, config = {}) {
|
|
22
|
+
const app = new Hono();
|
|
23
|
+
const {
|
|
24
|
+
jwtSecret,
|
|
25
|
+
jwtExpiresIn = '7d',
|
|
26
|
+
passphrase = 'secret',
|
|
27
|
+
allowRegistration = true
|
|
28
|
+
} = config;
|
|
29
|
+
|
|
30
|
+
// POST /auth/register - Register new user
|
|
31
|
+
if (allowRegistration) {
|
|
32
|
+
app.post('/register', asyncHandler(async (c) => {
|
|
33
|
+
const data = await c.req.json();
|
|
34
|
+
const { username, password, email, role = 'user' } = data;
|
|
35
|
+
|
|
36
|
+
// Validate input
|
|
37
|
+
if (!username || !password) {
|
|
38
|
+
const response = formatter.validationError([
|
|
39
|
+
{ field: 'username', message: 'Username is required' },
|
|
40
|
+
{ field: 'password', message: 'Password is required' }
|
|
41
|
+
]);
|
|
42
|
+
return c.json(response, response._status);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (password.length < 8) {
|
|
46
|
+
const response = formatter.validationError([
|
|
47
|
+
{ field: 'password', message: 'Password must be at least 8 characters' }
|
|
48
|
+
]);
|
|
49
|
+
return c.json(response, response._status);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check if username already exists
|
|
53
|
+
const existing = await usersResource.query({ username });
|
|
54
|
+
if (existing && existing.length > 0) {
|
|
55
|
+
const response = formatter.error('Username already exists', {
|
|
56
|
+
status: 409,
|
|
57
|
+
code: 'CONFLICT'
|
|
58
|
+
});
|
|
59
|
+
return c.json(response, response._status);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Create user
|
|
63
|
+
const user = await usersResource.insert({
|
|
64
|
+
username,
|
|
65
|
+
password, // Will be auto-encrypted by schema (secret field)
|
|
66
|
+
email,
|
|
67
|
+
role,
|
|
68
|
+
active: true,
|
|
69
|
+
apiKey: generateApiKey(),
|
|
70
|
+
createdAt: new Date().toISOString()
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Generate JWT token
|
|
74
|
+
let token = null;
|
|
75
|
+
if (jwtSecret) {
|
|
76
|
+
token = createToken(
|
|
77
|
+
{ userId: user.id, username: user.username, role: user.role },
|
|
78
|
+
jwtSecret,
|
|
79
|
+
jwtExpiresIn
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Remove sensitive data from response
|
|
84
|
+
const { password: _, ...userWithoutPassword } = user;
|
|
85
|
+
|
|
86
|
+
const response = formatter.created({
|
|
87
|
+
user: userWithoutPassword,
|
|
88
|
+
token
|
|
89
|
+
}, `/auth/users/${user.id}`);
|
|
90
|
+
|
|
91
|
+
return c.json(response, response._status);
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// POST /auth/login - Login with username/password
|
|
96
|
+
app.post('/login', asyncHandler(async (c) => {
|
|
97
|
+
const data = await c.req.json();
|
|
98
|
+
const { username, password } = data;
|
|
99
|
+
|
|
100
|
+
// Validate input
|
|
101
|
+
if (!username || !password) {
|
|
102
|
+
const response = formatter.unauthorized('Username and password are required');
|
|
103
|
+
return c.json(response, response._status);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Find user
|
|
107
|
+
const users = await usersResource.query({ username });
|
|
108
|
+
if (!users || users.length === 0) {
|
|
109
|
+
const response = formatter.unauthorized('Invalid credentials');
|
|
110
|
+
return c.json(response, response._status);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const user = users[0];
|
|
114
|
+
|
|
115
|
+
if (!user.active) {
|
|
116
|
+
const response = formatter.unauthorized('User account is inactive');
|
|
117
|
+
return c.json(response, response._status);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Verify password (decrypt and compare)
|
|
121
|
+
// Note: In production, use proper password hashing (bcrypt, argon2)
|
|
122
|
+
const [ok, err, decrypted] = await tryFn(() =>
|
|
123
|
+
user.password // Password is already decrypted by autoDecrypt
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// For secret fields, we need to manually decrypt if autoDecrypt is off
|
|
127
|
+
// But by default autoDecrypt is true, so user.password should be plain text here
|
|
128
|
+
// Let's just compare directly since schema handles encryption/decryption
|
|
129
|
+
const isValid = user.password === password;
|
|
130
|
+
|
|
131
|
+
if (!isValid) {
|
|
132
|
+
const response = formatter.unauthorized('Invalid credentials');
|
|
133
|
+
return c.json(response, response._status);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Update last login
|
|
137
|
+
await usersResource.update(user.id, {
|
|
138
|
+
lastLoginAt: new Date().toISOString()
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Generate JWT token
|
|
142
|
+
let token = null;
|
|
143
|
+
if (jwtSecret) {
|
|
144
|
+
token = createToken(
|
|
145
|
+
{ userId: user.id, username: user.username, role: user.role },
|
|
146
|
+
jwtSecret,
|
|
147
|
+
jwtExpiresIn
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Remove sensitive data from response
|
|
152
|
+
const { password: _, ...userWithoutPassword } = user;
|
|
153
|
+
|
|
154
|
+
const response = formatter.success({
|
|
155
|
+
user: userWithoutPassword,
|
|
156
|
+
token,
|
|
157
|
+
expiresIn: jwtExpiresIn
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return c.json(response, response._status);
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
// POST /auth/token/refresh - Refresh JWT token
|
|
164
|
+
if (jwtSecret) {
|
|
165
|
+
app.post('/token/refresh', asyncHandler(async (c) => {
|
|
166
|
+
const user = c.get('user');
|
|
167
|
+
|
|
168
|
+
if (!user) {
|
|
169
|
+
const response = formatter.unauthorized('Authentication required');
|
|
170
|
+
return c.json(response, response._status);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Generate new token
|
|
174
|
+
const token = createToken(
|
|
175
|
+
{ userId: user.id, username: user.username, role: user.role },
|
|
176
|
+
jwtSecret,
|
|
177
|
+
jwtExpiresIn
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const response = formatter.success({
|
|
181
|
+
token,
|
|
182
|
+
expiresIn: jwtExpiresIn
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return c.json(response, response._status);
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// GET /auth/me - Get current user info
|
|
190
|
+
app.get('/me', asyncHandler(async (c) => {
|
|
191
|
+
const user = c.get('user');
|
|
192
|
+
|
|
193
|
+
if (!user) {
|
|
194
|
+
const response = formatter.unauthorized('Authentication required');
|
|
195
|
+
return c.json(response, response._status);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// If user is from JWT payload (no password field), return as is
|
|
199
|
+
if (!user.password) {
|
|
200
|
+
const response = formatter.success(user);
|
|
201
|
+
return c.json(response, response._status);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Remove sensitive data
|
|
205
|
+
const { password: _, ...userWithoutPassword } = user;
|
|
206
|
+
|
|
207
|
+
const response = formatter.success(userWithoutPassword);
|
|
208
|
+
return c.json(response, response._status);
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
// POST /auth/api-key/regenerate - Regenerate API key
|
|
212
|
+
app.post('/api-key/regenerate', asyncHandler(async (c) => {
|
|
213
|
+
const user = c.get('user');
|
|
214
|
+
|
|
215
|
+
if (!user) {
|
|
216
|
+
const response = formatter.unauthorized('Authentication required');
|
|
217
|
+
return c.json(response, response._status);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Generate new API key
|
|
221
|
+
const newApiKey = generateApiKey();
|
|
222
|
+
|
|
223
|
+
// Update user
|
|
224
|
+
await usersResource.update(user.id, {
|
|
225
|
+
apiKey: newApiKey
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const response = formatter.success({
|
|
229
|
+
apiKey: newApiKey,
|
|
230
|
+
message: 'API key regenerated successfully'
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return c.json(response, response._status);
|
|
234
|
+
}));
|
|
235
|
+
|
|
236
|
+
return app;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export default {
|
|
240
|
+
createAuthRoutes
|
|
241
|
+
};
|