@molecule/api-database 1.0.0 → 1.0.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/README.md +727 -0
- package/package.json +6 -5
package/README.md
ADDED
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
3
|
+
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
|
+
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
|
+
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
+
Generated: 2026-08-04T01:47:58.914Z
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# @molecule/api-database
|
|
10
|
+
|
|
11
|
+
> **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
|
|
12
|
+
> It is written to be read by coding agents as much as by people, and is generated from this
|
|
13
|
+
> package's source — edit `src/index.ts` JSDoc, not this file.
|
|
14
|
+
|
|
15
|
+
Database core interface for molecule.dev.
|
|
16
|
+
|
|
17
|
+
Defines the standard interface for database providers, including both
|
|
18
|
+
raw connection pools and the database-agnostic DataStore abstraction.
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import {
|
|
24
|
+
setStore,
|
|
25
|
+
findById,
|
|
26
|
+
findMany,
|
|
27
|
+
create,
|
|
28
|
+
updateById,
|
|
29
|
+
deleteById,
|
|
30
|
+
} from '@molecule/api-database'
|
|
31
|
+
import { store } from '@molecule/api-database-postgresql'
|
|
32
|
+
|
|
33
|
+
// Wire the DataStore at app startup
|
|
34
|
+
setStore(store)
|
|
35
|
+
|
|
36
|
+
// CRUD operations — database-agnostic
|
|
37
|
+
const user = await findById<User>('users', userId)
|
|
38
|
+
|
|
39
|
+
const activeUsers = await findMany<User>('users', {
|
|
40
|
+
where: [{ field: 'status', operator: '=', value: 'active' }],
|
|
41
|
+
orderBy: [{ field: 'createdAt', direction: 'desc' }],
|
|
42
|
+
limit: 50,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
await create('users', { id, username, email })
|
|
46
|
+
await updateById('users', id, { name: 'New Name' })
|
|
47
|
+
await deleteById('users', id)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Type
|
|
51
|
+
|
|
52
|
+
`core`
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install @molecule/api-database @molecule/api-bond @molecule/api-i18n
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API
|
|
61
|
+
|
|
62
|
+
### Interfaces
|
|
63
|
+
|
|
64
|
+
#### `DatabaseConfig`
|
|
65
|
+
|
|
66
|
+
Database connection options (host, port, credentials, pool size, timeouts, SSL).
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
interface DatabaseConfig {
|
|
70
|
+
/**
|
|
71
|
+
* Database host.
|
|
72
|
+
*/
|
|
73
|
+
host?: string
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Database port.
|
|
77
|
+
*/
|
|
78
|
+
port?: number
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Database name.
|
|
82
|
+
*/
|
|
83
|
+
database?: string
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Database user.
|
|
87
|
+
*/
|
|
88
|
+
user?: string
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Database password.
|
|
92
|
+
*/
|
|
93
|
+
password?: string
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Connection string (alternative to individual fields).
|
|
97
|
+
*/
|
|
98
|
+
connectionString?: string
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Maximum number of connections in the pool.
|
|
102
|
+
*/
|
|
103
|
+
max?: number
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Minimum number of connections in the pool.
|
|
107
|
+
*/
|
|
108
|
+
min?: number
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Connection timeout in milliseconds.
|
|
112
|
+
*/
|
|
113
|
+
connectionTimeoutMillis?: number
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Idle timeout in milliseconds.
|
|
117
|
+
*/
|
|
118
|
+
idleTimeoutMillis?: number
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Enable SSL.
|
|
122
|
+
*/
|
|
123
|
+
ssl?:
|
|
124
|
+
| boolean
|
|
125
|
+
| {
|
|
126
|
+
rejectUnauthorized?: boolean
|
|
127
|
+
ca?: string
|
|
128
|
+
key?: string
|
|
129
|
+
cert?: string
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### `DatabaseConnection`
|
|
135
|
+
|
|
136
|
+
Database connection interface.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
interface DatabaseConnection {
|
|
140
|
+
/**
|
|
141
|
+
* Executes a parameterized query.
|
|
142
|
+
*
|
|
143
|
+
* @param text - SQL query text with placeholders ($1, $2, etc.)
|
|
144
|
+
* @param values - Parameter values
|
|
145
|
+
*/
|
|
146
|
+
query<T = Record<string, unknown>>(text: string, values?: unknown[]): Promise<QueryResult<T>>
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Releases the connection back to the pool.
|
|
150
|
+
*/
|
|
151
|
+
release(): void
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### `DatabasePool`
|
|
156
|
+
|
|
157
|
+
Database pool interface.
|
|
158
|
+
|
|
159
|
+
All database providers must implement this interface.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
interface DatabasePool {
|
|
163
|
+
/**
|
|
164
|
+
* Executes a parameterized query using a pool connection.
|
|
165
|
+
*
|
|
166
|
+
* @param text - SQL query text with placeholders ($1, $2, etc.)
|
|
167
|
+
* @param values - Parameter values
|
|
168
|
+
*/
|
|
169
|
+
query<T = Record<string, unknown>>(text: string, values?: unknown[]): Promise<QueryResult<T>>
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Acquires a connection from the pool.
|
|
173
|
+
*/
|
|
174
|
+
connect(): Promise<DatabaseConnection>
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Begins a transaction.
|
|
178
|
+
*/
|
|
179
|
+
transaction?(): Promise<DatabaseTransaction>
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Closes all connections in the pool.
|
|
183
|
+
*/
|
|
184
|
+
end(): Promise<void>
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Returns pool statistics (optional).
|
|
188
|
+
*/
|
|
189
|
+
stats?(): {
|
|
190
|
+
total: number
|
|
191
|
+
idle: number
|
|
192
|
+
waiting: number
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### `DatabaseProvider`
|
|
198
|
+
|
|
199
|
+
Database provider interface.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
interface DatabaseProvider {
|
|
203
|
+
/**
|
|
204
|
+
* The connection pool.
|
|
205
|
+
*/
|
|
206
|
+
pool: DatabasePool
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Creates a new pool with the given configuration.
|
|
210
|
+
*/
|
|
211
|
+
createPool?(config: DatabaseConfig): DatabasePool
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### `DatabaseTransaction`
|
|
216
|
+
|
|
217
|
+
Database transaction with commit and rollback (extends DatabaseConnection).
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
interface DatabaseTransaction extends DatabaseConnection {
|
|
221
|
+
/**
|
|
222
|
+
* Commits the transaction.
|
|
223
|
+
*/
|
|
224
|
+
commit(): Promise<void>
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Rolls back the transaction.
|
|
228
|
+
*/
|
|
229
|
+
rollback(): Promise<void>
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### `DataStore`
|
|
234
|
+
|
|
235
|
+
Abstract data store interface.
|
|
236
|
+
|
|
237
|
+
Provides database-agnostic CRUD methods. Each database bond
|
|
238
|
+
implements these using its native query language.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
interface DataStore {
|
|
242
|
+
/**
|
|
243
|
+
* Find a single record by its primary key.
|
|
244
|
+
*/
|
|
245
|
+
findById<T = Record<string, unknown>>(table: string, id: string | number): Promise<T | null>
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Find a single record matching conditions.
|
|
249
|
+
*/
|
|
250
|
+
findOne<T = Record<string, unknown>>(table: string, where: WhereCondition[]): Promise<T | null>
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Find many records with filtering, sorting, and pagination.
|
|
254
|
+
*/
|
|
255
|
+
findMany<T = Record<string, unknown>>(table: string, options?: FindManyOptions): Promise<T[]>
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Count records matching conditions.
|
|
259
|
+
*/
|
|
260
|
+
count(table: string, where?: WhereCondition[]): Promise<number>
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Insert a new record. Returns the inserted row.
|
|
264
|
+
*/
|
|
265
|
+
create<T = Record<string, unknown>>(
|
|
266
|
+
table: string,
|
|
267
|
+
data: Record<string, unknown>,
|
|
268
|
+
): Promise<MutationResult<T>>
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Update a record by primary key. Returns the updated row.
|
|
272
|
+
*/
|
|
273
|
+
updateById<T = Record<string, unknown>>(
|
|
274
|
+
table: string,
|
|
275
|
+
id: string | number,
|
|
276
|
+
data: Record<string, unknown>,
|
|
277
|
+
): Promise<MutationResult<T>>
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Update records matching conditions.
|
|
281
|
+
*/
|
|
282
|
+
updateMany(
|
|
283
|
+
table: string,
|
|
284
|
+
where: WhereCondition[],
|
|
285
|
+
data: Record<string, unknown>,
|
|
286
|
+
): Promise<MutationResult>
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Delete a record by primary key.
|
|
290
|
+
*/
|
|
291
|
+
deleteById(table: string, id: string | number): Promise<MutationResult>
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Delete records matching conditions.
|
|
295
|
+
*/
|
|
296
|
+
deleteMany(table: string, where: WhereCondition[]): Promise<MutationResult>
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### `FindManyOptions`
|
|
301
|
+
|
|
302
|
+
Options for findMany queries.
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
interface FindManyOptions {
|
|
306
|
+
where?: WhereCondition[]
|
|
307
|
+
orderBy?: OrderBy[]
|
|
308
|
+
limit?: number
|
|
309
|
+
offset?: number
|
|
310
|
+
select?: string[]
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### `MutationResult`
|
|
315
|
+
|
|
316
|
+
Result of a mutation operation.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
interface MutationResult<T = Record<string, unknown>> {
|
|
320
|
+
/** The affected/returned row, if any. */
|
|
321
|
+
data: T | null
|
|
322
|
+
/** Number of rows affected. */
|
|
323
|
+
affected: number
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
#### `OrderBy`
|
|
328
|
+
|
|
329
|
+
Column sort order (field name and ascending/descending direction).
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
interface OrderBy {
|
|
333
|
+
field: string
|
|
334
|
+
direction: 'asc' | 'desc'
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### `QueryResult`
|
|
339
|
+
|
|
340
|
+
Query result with rows and metadata.
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
interface QueryResult<T = Record<string, unknown>> {
|
|
344
|
+
/**
|
|
345
|
+
* Array of rows returned by the query.
|
|
346
|
+
*/
|
|
347
|
+
rows: T[]
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Number of rows affected (for INSERT, UPDATE, DELETE).
|
|
351
|
+
*/
|
|
352
|
+
rowCount: number | null
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Column metadata (optional, provider-dependent).
|
|
356
|
+
*/
|
|
357
|
+
fields?: Array<{
|
|
358
|
+
name: string
|
|
359
|
+
dataTypeID?: number
|
|
360
|
+
}>
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### `WhereCondition`
|
|
365
|
+
|
|
366
|
+
Filter condition for queries.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
interface WhereCondition {
|
|
370
|
+
field: string
|
|
371
|
+
operator:
|
|
372
|
+
| '='
|
|
373
|
+
| '!='
|
|
374
|
+
| '>'
|
|
375
|
+
| '<'
|
|
376
|
+
| '>='
|
|
377
|
+
| '<='
|
|
378
|
+
| 'in'
|
|
379
|
+
| 'not_in'
|
|
380
|
+
/**
|
|
381
|
+
* Case-insensitive SQL pattern match. `value` is used AS-IS as the raw
|
|
382
|
+
* LIKE pattern: `%` matches any run of characters, `_` matches any
|
|
383
|
+
* single character, and the CALLER is responsible for escaping any
|
|
384
|
+
* literal `%` / `_` / `\` they don't want treated as wildcards. All
|
|
385
|
+
* three first-party bonds (postgresql, mysql, sqlite) implement `like`
|
|
386
|
+
* case-INSENSITIVELY — postgres via `ILIKE`, mysql/sqlite via
|
|
387
|
+
* `LOWER()` on both sides — specifically so this operator has
|
|
388
|
+
* IDENTICAL results across every bond regardless of column collation
|
|
389
|
+
* or `PRAGMA case_sensitive_like`. Prior to this contract the postgres
|
|
390
|
+
* bond escaped the value (making `like` an exact-match operator there
|
|
391
|
+
* only) while sqlite/mysql passed it through raw — the same `{
|
|
392
|
+
* operator: 'like', value: `%${search}%` }` filter silently matched
|
|
393
|
+
* nothing on postgres and worked everywhere else. For human-typed
|
|
394
|
+
* search input where the value should be a literal substring (not a
|
|
395
|
+
* pattern the caller controls), use `ilike` instead — it escapes
|
|
396
|
+
* wildcards and wraps `%…%` for you, so it is injection-safe for
|
|
397
|
+
* arbitrary user text.
|
|
398
|
+
*/
|
|
399
|
+
| 'like'
|
|
400
|
+
/**
|
|
401
|
+
* Case-insensitive substring match. The value is treated as a
|
|
402
|
+
* literal substring (LIKE wildcards in the value are escaped) and
|
|
403
|
+
* the bond wraps it with `%…%` to perform a contains-style match.
|
|
404
|
+
* Use this for human-typed search input where case shouldn't matter.
|
|
405
|
+
*/
|
|
406
|
+
| 'ilike'
|
|
407
|
+
| 'is_null'
|
|
408
|
+
| 'is_not_null'
|
|
409
|
+
value?: unknown
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Functions
|
|
414
|
+
|
|
415
|
+
#### `connect()`
|
|
416
|
+
|
|
417
|
+
Acquires a dedicated connection from the bonded pool. The connection must
|
|
418
|
+
be released after use by calling `connection.release()`.
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
function connect(): Promise<DatabaseConnection>
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Returns:** A database connection that must be released after use.
|
|
425
|
+
|
|
426
|
+
#### `count(table, where)`
|
|
427
|
+
|
|
428
|
+
Counts records matching the given filter conditions, or all records
|
|
429
|
+
if no conditions are specified.
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
function count(table: string, where?: WhereCondition[]): Promise<number>
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
- `table` — The database table name.
|
|
436
|
+
- `where` — Optional filter conditions.
|
|
437
|
+
|
|
438
|
+
**Returns:** The number of matching records.
|
|
439
|
+
|
|
440
|
+
#### `create(table, data)`
|
|
441
|
+
|
|
442
|
+
Inserts a new record into the table. Returns the inserted row and
|
|
443
|
+
the number of affected rows (always 1 on success).
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
function create(table: string, data: Record<string, unknown>): Promise<MutationResult<T>>
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
- `table` — The database table name.
|
|
450
|
+
- `data` — The column values to insert as key-value pairs.
|
|
451
|
+
|
|
452
|
+
**Returns:** A `MutationResult` with the inserted row and affected count.
|
|
453
|
+
|
|
454
|
+
#### `deleteById(table, id)`
|
|
455
|
+
|
|
456
|
+
Deletes a record identified by its primary key.
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
function deleteById(
|
|
460
|
+
table: string,
|
|
461
|
+
id: string | number,
|
|
462
|
+
): Promise<MutationResult<Record<string, unknown>>>
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
- `table` — The database table name.
|
|
466
|
+
- `id` — The primary key value of the record to delete.
|
|
467
|
+
|
|
468
|
+
**Returns:** A `MutationResult` with the affected count (1 if deleted, 0 if not found).
|
|
469
|
+
|
|
470
|
+
#### `deleteMany(table, where)`
|
|
471
|
+
|
|
472
|
+
Deletes all records matching the given filter conditions.
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
function deleteMany(
|
|
476
|
+
table: string,
|
|
477
|
+
where: WhereCondition[],
|
|
478
|
+
): Promise<MutationResult<Record<string, unknown>>>
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
- `table` — The database table name.
|
|
482
|
+
- `where` — Filter conditions to select records to delete.
|
|
483
|
+
|
|
484
|
+
**Returns:** A `MutationResult` with the number of deleted rows.
|
|
485
|
+
|
|
486
|
+
#### `end()`
|
|
487
|
+
|
|
488
|
+
Closes all connections in the bonded pool. Call during graceful shutdown.
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
function end(): Promise<void>
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**Returns:** A promise that resolves when all connections have been closed.
|
|
495
|
+
|
|
496
|
+
#### `findById(table, id)`
|
|
497
|
+
|
|
498
|
+
Finds a single record by its primary key (`id` column).
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
function findById(table: string, id: string | number): Promise<T | null>
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
- `table` — The database table name.
|
|
505
|
+
- `id` — The primary key value to look up.
|
|
506
|
+
|
|
507
|
+
**Returns:** The matching record cast to `T`, or `null` if not found.
|
|
508
|
+
|
|
509
|
+
#### `findMany(table, options)`
|
|
510
|
+
|
|
511
|
+
Finds multiple records with optional filtering, sorting, pagination,
|
|
512
|
+
and column selection.
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
function findMany(table: string, options?: FindManyOptions): Promise<T[]>
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
- `table` — The database table name.
|
|
519
|
+
- `options` — Query options including `where`, `orderBy`, `limit`, `offset`, and `select`.
|
|
520
|
+
|
|
521
|
+
**Returns:** Array of matching records cast to `T`.
|
|
522
|
+
|
|
523
|
+
#### `findOne(table, where)`
|
|
524
|
+
|
|
525
|
+
Finds a single record matching the given filter conditions. Returns the
|
|
526
|
+
first match if multiple rows satisfy the conditions.
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
function findOne(table: string, where: WhereCondition[]): Promise<T | null>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
- `table` — The database table name.
|
|
533
|
+
- `where` — Array of filter conditions to match against.
|
|
534
|
+
|
|
535
|
+
**Returns:** The matching record cast to `T`, or `null` if none found.
|
|
536
|
+
|
|
537
|
+
#### `getPool()`
|
|
538
|
+
|
|
539
|
+
Retrieves the bonded database pool, throwing if none is configured.
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
function getPool(): DatabasePool
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**Returns:** The bonded database pool.
|
|
546
|
+
|
|
547
|
+
#### `getStore()`
|
|
548
|
+
|
|
549
|
+
Retrieves the bonded DataStore, throwing if none is configured.
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
function getStore(): DataStore
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**Returns:** The bonded DataStore implementation.
|
|
556
|
+
|
|
557
|
+
#### `hasPool()`
|
|
558
|
+
|
|
559
|
+
Checks whether a database pool is currently bonded.
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
function hasPool(): boolean
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**Returns:** `true` if a database pool is bonded.
|
|
566
|
+
|
|
567
|
+
#### `hasStore()`
|
|
568
|
+
|
|
569
|
+
Checks whether a DataStore is currently bonded.
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
function hasStore(): boolean
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
**Returns:** `true` if a DataStore is bonded.
|
|
576
|
+
|
|
577
|
+
#### `query(text, values)`
|
|
578
|
+
|
|
579
|
+
Executes a parameterized SQL query using the bonded pool.
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
function query(text: string, values?: unknown[]): Promise<QueryResult<T>>
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
- `text` — SQL query text with placeholders (`$1`, `$2`, etc.).
|
|
586
|
+
- `values` — Parameter values corresponding to the placeholders.
|
|
587
|
+
|
|
588
|
+
**Returns:** The query result containing `rows`, `rowCount`, and optional `fields`.
|
|
589
|
+
|
|
590
|
+
#### `setPool(pool)`
|
|
591
|
+
|
|
592
|
+
Registers a database connection pool as the active singleton. Called by
|
|
593
|
+
bond packages during application startup.
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
function setPool(pool: DatabasePool): void
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
- `pool` — The database pool implementation to bond.
|
|
600
|
+
|
|
601
|
+
#### `setStore(store)`
|
|
602
|
+
|
|
603
|
+
Registers a DataStore implementation as the active singleton. Called by
|
|
604
|
+
bond packages during application startup.
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
function setStore(store: DataStore): void
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
- `store` — The DataStore implementation to bond.
|
|
611
|
+
|
|
612
|
+
#### `updateById(table, id, data)`
|
|
613
|
+
|
|
614
|
+
Updates a record identified by its primary key. Returns the updated row
|
|
615
|
+
and the number of affected rows.
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
function updateById(
|
|
619
|
+
table: string,
|
|
620
|
+
id: string | number,
|
|
621
|
+
data: Record<string, unknown>,
|
|
622
|
+
): Promise<MutationResult<T>>
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
- `table` — The database table name.
|
|
626
|
+
- `id` — The primary key value of the record to update.
|
|
627
|
+
- `data` — The column values to update as key-value pairs.
|
|
628
|
+
|
|
629
|
+
**Returns:** A `MutationResult` with the updated row and affected count.
|
|
630
|
+
|
|
631
|
+
#### `updateMany(table, where, data)`
|
|
632
|
+
|
|
633
|
+
Updates all records matching the given filter conditions.
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
function updateMany(
|
|
637
|
+
table: string,
|
|
638
|
+
where: WhereCondition[],
|
|
639
|
+
data: Record<string, unknown>,
|
|
640
|
+
): Promise<MutationResult<Record<string, unknown>>>
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
- `table` — The database table name.
|
|
644
|
+
- `where` — Filter conditions to select records to update.
|
|
645
|
+
- `data` — The column values to update as key-value pairs.
|
|
646
|
+
|
|
647
|
+
**Returns:** A `MutationResult` with the affected count.
|
|
648
|
+
|
|
649
|
+
## Available Providers
|
|
650
|
+
|
|
651
|
+
| Provider | Package |
|
|
652
|
+
| ---------- | ----------------------------------- |
|
|
653
|
+
| MySQL | `@molecule/api-database-mysql` |
|
|
654
|
+
| PostgreSQL | `@molecule/api-database-postgresql` |
|
|
655
|
+
| SQLite | `@molecule/api-database-sqlite` |
|
|
656
|
+
|
|
657
|
+
## Injection Notes
|
|
658
|
+
|
|
659
|
+
### Requirements
|
|
660
|
+
|
|
661
|
+
Peer dependencies:
|
|
662
|
+
|
|
663
|
+
- `@molecule/api-bond` ^1.0.1
|
|
664
|
+
- `@molecule/api-i18n` ^1.0.1
|
|
665
|
+
|
|
666
|
+
### Runtime Dependencies
|
|
667
|
+
|
|
668
|
+
- `@molecule/api-bond`
|
|
669
|
+
- `@molecule/api-i18n`
|
|
670
|
+
|
|
671
|
+
Data-access contract — the rules code generators most often get wrong:
|
|
672
|
+
|
|
673
|
+
- **Scope EVERY query by owner — the #1 security rule.** A user must never read or write
|
|
674
|
+
another user's rows. Add `{ field: 'user_id', operator: '=', value: getUserId(res) }` to
|
|
675
|
+
every list/read `where`, and for a single-row route load the row and 404 if it isn't the
|
|
676
|
+
caller's (404, not 403 — don't leak existence). An unscoped `findById`/`updateById`/
|
|
677
|
+
`deleteById` on a client-supplied id is an IDOR (one user edits another's data). See the
|
|
678
|
+
`auth` skill.
|
|
679
|
+
- **CRUD goes through the exported data functions** (`findById`, `findOne`,
|
|
680
|
+
`findMany`, `count`, `create`, `updateById`, `deleteById`). Filter with a
|
|
681
|
+
`where` ARRAY of `{ field, operator, value }`:
|
|
682
|
+
`findMany('plants', { where: [{ field: 'speciesId', operator: '=', value: id }], limit: 50 })`.
|
|
683
|
+
A bare key object — `findMany('plants', { speciesId: id })` — is NOT a valid
|
|
684
|
+
`FindManyOptions` and will not filter.
|
|
685
|
+
- **Raw SQL: import the standalone `query(sql, values)` from this package** —
|
|
686
|
+
NOT `getStore().query()`. The DataStore has no `query`; raw parameterized
|
|
687
|
+
query lives on the pool/provider. The store also has NO `.exec()`,
|
|
688
|
+
`.prepare()`, `.run()`, `.get()`, or `.all()` — those are driver methods
|
|
689
|
+
(better-sqlite3 / pg) and calling them fails the type-check.
|
|
690
|
+
- **Atomic multi-write = a MANUAL transaction on ONE connection; the store
|
|
691
|
+
helpers do NOT join it.** When several writes must be all-or-nothing (move
|
|
692
|
+
funds between two accounts, create an order AND decrement its stock, accept a
|
|
693
|
+
booking AND mark the slot taken), acquire a dedicated connection and drive
|
|
694
|
+
raw SQL on IT: `const conn = await connect()` then
|
|
695
|
+
`try { await conn.query('BEGIN'); …writes…; await conn.query('COMMIT') }`
|
|
696
|
+
`catch (e) { await conn.query('ROLLBACK').catch(() => {}); throw e }`
|
|
697
|
+
`finally { conn.release() }`. **The load-bearing gotcha:** the exported
|
|
698
|
+
`create`/`updateById`/`findMany`/`deleteById` run on the POOL's own
|
|
699
|
+
connection and commit INDEPENDENTLY — calling them between your `BEGIN` and
|
|
700
|
+
`COMMIT` does NOT enroll them in the transaction, so a mid-way failure leaves
|
|
701
|
+
those writes committed while the rest rolls back (a half-applied transfer).
|
|
702
|
+
Inside a transaction do every read AND write with `conn.query(sql, values)`
|
|
703
|
+
on that same `conn`, and ALWAYS `release()` in `finally` — a leaked
|
|
704
|
+
connection permanently drains the pool. Prefer this only for true multi-row
|
|
705
|
+
invariants; a single-row update is already atomic via `updateById`.
|
|
706
|
+
- **Create tables only via timestamped `.sql` files in `migrations/`** (the
|
|
707
|
+
migration runner applies them on startup) — NEVER programmatically through
|
|
708
|
+
the store. Seed rows with `create()`.
|
|
709
|
+
- **Every `id` is a UUID string** (the resource layer sets `id = id || uuid()`
|
|
710
|
+
on create). So every primary key AND foreign key is a UUID-string column:
|
|
711
|
+
`id TEXT PRIMARY KEY` (SQLite) / `id UUID PRIMARY KEY` (Postgres); FKs
|
|
712
|
+
likewise (`user_id TEXT` / `user_id UUID`). NEVER `INTEGER PRIMARY KEY
|
|
713
|
+
AUTOINCREMENT` or `SERIAL` — inserting a UUID string into an integer key
|
|
714
|
+
fails at runtime (`datatype mismatch`) and breaks every create endpoint.
|
|
715
|
+
- **`like` vs `ilike` — pick by who controls the wildcards.** Both are
|
|
716
|
+
case-insensitive on every bond (identical results across postgresql/
|
|
717
|
+
mysql/sqlite — see the `WhereCondition['operator']` JSDoc for exactly how
|
|
718
|
+
each bond gets there). `like` passes `value` through as a raw SQL
|
|
719
|
+
pattern — YOU write the `%`/`_`; use it when you already built the
|
|
720
|
+
pattern (e.g. a fixed prefix search `'admin_%'`). `ilike` treats `value`
|
|
721
|
+
as a literal substring, escapes it, and wraps `%…%` for you — use it for
|
|
722
|
+
ANY human-typed search box (`{ operator: 'ilike', value: userInput }`),
|
|
723
|
+
never `like` with a caller-built `%${userInput}%` string.
|
|
724
|
+
|
|
725
|
+
## Translations
|
|
726
|
+
|
|
727
|
+
Translation strings are provided by `@molecule/api-locales-database`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@molecule/api-database",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Database core interface for molecule.dev",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
21
22
|
],
|
|
22
23
|
"keywords": [
|
|
23
24
|
"molecule",
|
|
@@ -28,11 +29,11 @@
|
|
|
28
29
|
],
|
|
29
30
|
"license": "Apache-2.0",
|
|
30
31
|
"peerDependencies": {
|
|
31
|
-
"@molecule/api-bond": "^1.0.
|
|
32
|
-
"@molecule/api-i18n": "^1.0.
|
|
32
|
+
"@molecule/api-bond": "^1.0.1",
|
|
33
|
+
"@molecule/api-i18n": "^1.0.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@molecule/api-bond": "1.0.
|
|
36
|
+
"@molecule/api-bond": "1.0.1",
|
|
36
37
|
"@types/node": "26.1.2",
|
|
37
38
|
"typescript": "6.0.3",
|
|
38
39
|
"vitest": "4.1.10"
|