@chidchanun/bcp 0.1.24 → 0.1.26
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 +587 -202
- package/docs/README.md +278 -86
- package/docs/file-upload.md +194 -32
- package/docs/releases/0.1.25.md +132 -0
- package/docs/releases/0.1.26.md +281 -0
- package/docs/s3-storage.md +276 -0
- package/docs/storage.md +401 -0
- package/package.json +5 -2
- package/packages/client/src/database.mjs +236 -0
- package/packages/client/src/server.ts +46 -0
- package/packages/server/src/file-delivery.ts +613 -0
- package/packages/server/src/storage-s3.ts +1159 -0
- package/packages/server/src/storage.ts +1515 -0
- package/packages/server/src/upload-stream.ts +846 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// packages/client/src/database.ts
|
|
2
|
+
var MYSQL_MODULE_SPECIFIER = "mysql2/promise";
|
|
3
|
+
var BcpDatabase = class {
|
|
4
|
+
options;
|
|
5
|
+
poolPromise = null;
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.options = {
|
|
8
|
+
...options
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
async query(sql, parameters) {
|
|
12
|
+
assertSql(sql);
|
|
13
|
+
const pool = await this.getPool();
|
|
14
|
+
const [rows] = await pool.query(
|
|
15
|
+
sql,
|
|
16
|
+
parameters
|
|
17
|
+
);
|
|
18
|
+
return rows;
|
|
19
|
+
}
|
|
20
|
+
async execute(sql, parameters) {
|
|
21
|
+
assertSql(sql);
|
|
22
|
+
const pool = await this.getPool();
|
|
23
|
+
const [result] = await pool.execute(
|
|
24
|
+
sql,
|
|
25
|
+
parameters
|
|
26
|
+
);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
async transaction(callback) {
|
|
30
|
+
if (typeof callback !== "function") {
|
|
31
|
+
throw new TypeError(
|
|
32
|
+
"BCP Database: transaction callback must be a function."
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const pool = await this.getPool();
|
|
36
|
+
const connection = await pool.getConnection();
|
|
37
|
+
await connection.beginTransaction();
|
|
38
|
+
const transactionDatabase = {
|
|
39
|
+
query: async (sql, parameters) => {
|
|
40
|
+
assertSql(sql);
|
|
41
|
+
const [rows] = await connection.query(
|
|
42
|
+
sql,
|
|
43
|
+
parameters
|
|
44
|
+
);
|
|
45
|
+
return rows;
|
|
46
|
+
},
|
|
47
|
+
execute: async (sql, parameters) => {
|
|
48
|
+
assertSql(sql);
|
|
49
|
+
const [result] = await connection.execute(
|
|
50
|
+
sql,
|
|
51
|
+
parameters
|
|
52
|
+
);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
try {
|
|
57
|
+
const result = await callback(
|
|
58
|
+
transactionDatabase
|
|
59
|
+
);
|
|
60
|
+
await connection.commit();
|
|
61
|
+
return result;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
try {
|
|
64
|
+
await connection.rollback();
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
} finally {
|
|
69
|
+
connection.release();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async close() {
|
|
73
|
+
const pendingPool = this.poolPromise;
|
|
74
|
+
this.poolPromise = null;
|
|
75
|
+
if (!pendingPool) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const pool = await pendingPool;
|
|
79
|
+
await pool.end();
|
|
80
|
+
}
|
|
81
|
+
getPool() {
|
|
82
|
+
if (!this.poolPromise) {
|
|
83
|
+
this.poolPromise = createMysqlPool(
|
|
84
|
+
resolveDatabaseOptions(
|
|
85
|
+
this.options
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return this.poolPromise;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
function createDatabase(options = {}) {
|
|
93
|
+
return new BcpDatabase(
|
|
94
|
+
options
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
function resolveDatabaseOptions(options = {}, environment = process.env) {
|
|
98
|
+
const driver = options.driver ?? normalizeDriver(
|
|
99
|
+
environment.DB_DRIVER ?? "mysql"
|
|
100
|
+
);
|
|
101
|
+
if (driver !== "mysql") {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`BCP Database: unsupported database driver "${driver}".`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
driver,
|
|
108
|
+
host: nonEmpty(
|
|
109
|
+
options.host ?? environment.DB_HOST
|
|
110
|
+
) ?? "localhost",
|
|
111
|
+
port: resolvePositiveInteger(
|
|
112
|
+
options.port ?? environment.DB_PORT,
|
|
113
|
+
3306,
|
|
114
|
+
"DB_PORT"
|
|
115
|
+
),
|
|
116
|
+
user: nonEmpty(
|
|
117
|
+
options.user ?? environment.DB_USER
|
|
118
|
+
) ?? "root",
|
|
119
|
+
password: String(
|
|
120
|
+
options.password ?? environment.DB_PASSWORD ?? ""
|
|
121
|
+
),
|
|
122
|
+
database: nonEmpty(
|
|
123
|
+
options.database ?? environment.DB_NAME
|
|
124
|
+
) ?? "bcp_app",
|
|
125
|
+
connectionLimit: resolvePositiveInteger(
|
|
126
|
+
options.connectionLimit ?? environment.DB_CONNECTION_LIMIT,
|
|
127
|
+
10,
|
|
128
|
+
"DB_CONNECTION_LIMIT"
|
|
129
|
+
),
|
|
130
|
+
waitForConnections: options.waitForConnections ?? resolveBoolean(
|
|
131
|
+
environment.DB_WAIT_FOR_CONNECTIONS,
|
|
132
|
+
true,
|
|
133
|
+
"DB_WAIT_FOR_CONNECTIONS"
|
|
134
|
+
),
|
|
135
|
+
queueLimit: resolveNonNegativeInteger(
|
|
136
|
+
options.queueLimit ?? environment.DB_QUEUE_LIMIT,
|
|
137
|
+
0,
|
|
138
|
+
"DB_QUEUE_LIMIT"
|
|
139
|
+
),
|
|
140
|
+
charset: nonEmpty(
|
|
141
|
+
options.charset ?? environment.DB_CHARSET
|
|
142
|
+
) ?? "utf8mb4"
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
var db = createDatabase();
|
|
146
|
+
async function createMysqlPool(options) {
|
|
147
|
+
let mysqlModule;
|
|
148
|
+
try {
|
|
149
|
+
mysqlModule = await import(MYSQL_MODULE_SPECIFIER);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
"BCP Database: MySQL requires the optional dependency mysql2. Install it with `npm install mysql2` or create the app with the MySQL preset.",
|
|
153
|
+
{
|
|
154
|
+
cause: error
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return mysqlModule.createPool({
|
|
159
|
+
host: options.host,
|
|
160
|
+
port: options.port,
|
|
161
|
+
user: options.user,
|
|
162
|
+
password: options.password,
|
|
163
|
+
database: options.database,
|
|
164
|
+
waitForConnections: options.waitForConnections,
|
|
165
|
+
connectionLimit: options.connectionLimit,
|
|
166
|
+
queueLimit: options.queueLimit,
|
|
167
|
+
charset: options.charset
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function normalizeDriver(value) {
|
|
171
|
+
const normalized = value.trim().toLowerCase();
|
|
172
|
+
if (normalized === "mysql") {
|
|
173
|
+
return "mysql";
|
|
174
|
+
}
|
|
175
|
+
throw new Error(
|
|
176
|
+
`BCP Database: unsupported database driver "${value}".`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
function nonEmpty(value) {
|
|
180
|
+
if (typeof value !== "string") {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const normalized = value.trim();
|
|
184
|
+
return normalized || null;
|
|
185
|
+
}
|
|
186
|
+
function resolvePositiveInteger(value, fallback, label) {
|
|
187
|
+
if (value === void 0 || value === null || value === "") {
|
|
188
|
+
return fallback;
|
|
189
|
+
}
|
|
190
|
+
const parsed = Number(value);
|
|
191
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`BCP Database: ${label} must be a positive integer.`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return parsed;
|
|
197
|
+
}
|
|
198
|
+
function resolveNonNegativeInteger(value, fallback, label) {
|
|
199
|
+
if (value === void 0 || value === null || value === "") {
|
|
200
|
+
return fallback;
|
|
201
|
+
}
|
|
202
|
+
const parsed = Number(value);
|
|
203
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`BCP Database: ${label} must be a non-negative integer.`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return parsed;
|
|
209
|
+
}
|
|
210
|
+
function resolveBoolean(value, fallback, label) {
|
|
211
|
+
if (value === void 0 || value === null || value === "") {
|
|
212
|
+
return fallback;
|
|
213
|
+
}
|
|
214
|
+
if (value === true || value === "1" || value === "true") {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
if (value === false || value === "0" || value === "false") {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
throw new Error(
|
|
221
|
+
`BCP Database: ${label} must be true/false or 1/0.`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
function assertSql(sql) {
|
|
225
|
+
if (typeof sql !== "string" || sql.trim() === "") {
|
|
226
|
+
throw new TypeError(
|
|
227
|
+
"BCP Database: SQL must be a non-empty string."
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
export {
|
|
232
|
+
BcpDatabase,
|
|
233
|
+
createDatabase,
|
|
234
|
+
db,
|
|
235
|
+
resolveDatabaseOptions
|
|
236
|
+
};
|
|
@@ -46,6 +46,52 @@ export {
|
|
|
46
46
|
type UploadErrorCode,
|
|
47
47
|
} from "../../server/src/upload.js";
|
|
48
48
|
|
|
49
|
+
export {
|
|
50
|
+
storeMultipartFile,
|
|
51
|
+
|
|
52
|
+
type MultipartStorageFileInfo,
|
|
53
|
+
type MultipartStorageUploadOptions,
|
|
54
|
+
type StoredMultipartFile,
|
|
55
|
+
} from "../../server/src/upload-stream.js";
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
createLocalStorage,
|
|
59
|
+
getStorageCapabilities,
|
|
60
|
+
normalizeStorageKey,
|
|
61
|
+
putStorageStream,
|
|
62
|
+
readStorageStream,
|
|
63
|
+
StorageError,
|
|
64
|
+
storeUploadedFile,
|
|
65
|
+
|
|
66
|
+
type LocalStorageOptions,
|
|
67
|
+
type StorageAdapter,
|
|
68
|
+
type StorageAdapterCapabilities,
|
|
69
|
+
type StorageErrorCode,
|
|
70
|
+
type StorageObjectMetadata,
|
|
71
|
+
type StoragePutOptions,
|
|
72
|
+
type StoragePutStreamOptions,
|
|
73
|
+
type StorageReadableStream,
|
|
74
|
+
type StorageReadOptions,
|
|
75
|
+
type StorageWriteValue,
|
|
76
|
+
type StoredUploadedObject,
|
|
77
|
+
type StoreUploadedFileOptions,
|
|
78
|
+
} from "../../server/src/storage.js";
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
createS3Storage,
|
|
82
|
+
|
|
83
|
+
type S3StorageAdapter,
|
|
84
|
+
type S3StorageMultipartOptions,
|
|
85
|
+
type S3StorageOptions,
|
|
86
|
+
} from "../../server/src/storage-s3.js";
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
createStorageResponse,
|
|
90
|
+
isStorageRangeError,
|
|
91
|
+
|
|
92
|
+
type StorageResponseOptions,
|
|
93
|
+
} from "../../server/src/file-delivery.js";
|
|
94
|
+
|
|
49
95
|
export {
|
|
50
96
|
json,
|
|
51
97
|
redirect,
|