Skip to content

Commit d401efe

Browse files
authored
Add support for sync databases (#58)
1 parent 9c5e78b commit d401efe

File tree

12 files changed

+199
-106
lines changed

12 files changed

+199
-106
lines changed

docs/pages/databases/cloudflare-do.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33
## Write queries within your DO
44

5+
Because of the nature of Durable Objects, all sql query are executed sync, so there is no need for you to await the execute!
6+
57
```ts
68
import { DOQB } from 'workers-qb'
79

810
export class DOSRS extends DurableObject {
9-
async getEmployees() {
11+
getEmployees() {
1012
const qb = new DOQB(this.ctx.storage.sql)
1113

12-
const fetched = await qb
14+
const fetched = qb
1315
.fetchAll({
1416
tableName: 'employees',
1517
})
@@ -19,3 +21,30 @@ export class DOSRS extends DurableObject {
1921
}
2022
}
2123
```
24+
25+
## Transactions
26+
27+
Durable objects support transaction queries
28+
29+
```ts
30+
import { DOQB } from 'workers-qb'
31+
32+
export class DOSRS extends DurableObject {
33+
getEmployeePayments() {
34+
// you can also use `transaction`, which is useful for multiple queries:
35+
this.ctx.storage.transactionSync(() => {
36+
// `execute` blocks synchronously until the query has been executed
37+
// this is avaliable on DODB (and might not be on other providers)
38+
const fetched = qb
39+
.fetchAll({
40+
tableName: 'employees',
41+
})
42+
.execute()
43+
44+
const result = fetched.map((val) => qb.select('payments').where('name = ?', val.name).execute())
45+
46+
return result
47+
})
48+
}
49+
}
50+
```

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "workers-qb",
3-
"version": "1.5.2",
3+
"version": "1.6.0",
44
"description": "Zero dependencies Query Builder for Cloudflare Workers",
55
"main": "./dist/index.js",
66
"module": "./dist/index.mjs",

src/builder.ts

Lines changed: 41 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
InsertOne,
1212
InsertWithoutReturning,
1313
Join,
14+
MaybeAsync,
1415
OneResult,
1516
QueryBuilderOptions,
1617
QueryLoggerMeta,
@@ -28,11 +29,13 @@ import {
2829
import { ConflictTypes, FetchTypes, OrderTypes } from './enums'
2930
import { Query, QueryWithExtra, Raw } from './tools'
3031
import { SelectBuilder } from './modularBuilder'
32+
import { asyncLoggerWrapper, defaultLogger } from './logger'
3133

32-
export class QueryBuilder<GenericResultWrapper> {
33-
protected options: QueryBuilderOptions
34+
export class QueryBuilder<GenericResultWrapper, IsAsync extends boolean = true> {
35+
protected options: QueryBuilderOptions<IsAsync>
36+
loggerWrapper = asyncLoggerWrapper
3437

35-
constructor(options?: QueryBuilderOptions) {
38+
constructor(options?: QueryBuilderOptions<IsAsync>) {
3639
this.options = options || {}
3740
}
3841

@@ -43,48 +46,27 @@ export class QueryBuilder<GenericResultWrapper> {
4346
return
4447
}
4548

46-
this.options.logger = (query: RawQuery, meta: QueryLoggerMeta) => {
47-
console.log(`[workers-qb][${meta.duration}ms] ${JSON.stringify(query)}`)
48-
}
49+
this.options.logger = defaultLogger
4950
} else {
5051
this.options.logger = undefined
5152
}
5253
}
5354

54-
async loggerWrapper(query: Query | Query[], innerFunction: () => any) {
55-
const start = Date.now()
56-
try {
57-
return await innerFunction()
58-
} catch (e) {
59-
throw e
60-
} finally {
61-
if (this.options.logger) {
62-
if (Array.isArray(query)) {
63-
for (const q of query) {
64-
await this.options.logger(q.toObject(), { duration: Date.now() - start })
65-
}
66-
} else {
67-
await this.options.logger(query.toObject(), { duration: Date.now() - start })
68-
}
69-
}
70-
}
71-
}
72-
73-
async execute(query: Query): Promise<any> {
55+
execute(query: Query<any, IsAsync>): MaybeAsync<IsAsync, any> {
7456
throw new Error('Execute method not implemented')
7557
}
7658

77-
async batchExecute(queryArray: Query[]): Promise<any[]> {
59+
batchExecute(queryArray: Query<any, IsAsync>[]): MaybeAsync<IsAsync, any[]> {
7860
throw new Error('Batch execute method not implemented')
7961
}
8062

8163
createTable<GenericResult = undefined>(params: {
8264
tableName: string
8365
schema: string
8466
ifNotExists?: boolean
85-
}): Query<ArrayResult<GenericResultWrapper, GenericResult>> {
67+
}): Query<ArrayResult<GenericResultWrapper, GenericResult>, IsAsync> {
8668
return new Query(
87-
(q: Query) => {
69+
(q) => {
8870
return this.execute(q)
8971
},
9072
`CREATE TABLE ${params.ifNotExists ? 'IF NOT EXISTS' : ''} ${params.tableName}
@@ -95,14 +77,16 @@ export class QueryBuilder<GenericResultWrapper> {
9577
dropTable<GenericResult = undefined>(params: {
9678
tableName: string
9779
ifExists?: boolean
98-
}): Query<ArrayResult<GenericResultWrapper, GenericResult>> {
99-
return new Query((q: Query) => {
80+
}): Query<ArrayResult<GenericResultWrapper, GenericResult>, IsAsync> {
81+
return new Query((q) => {
10082
return this.execute(q)
10183
}, `DROP TABLE ${params.ifExists ? 'IF EXISTS' : ''} ${params.tableName}`)
10284
}
10385

104-
select<GenericResult = DefaultReturnObject>(tableName: string): SelectBuilder<GenericResultWrapper, GenericResult> {
105-
return new SelectBuilder<GenericResultWrapper, GenericResult>(
86+
select<GenericResult = DefaultReturnObject>(
87+
tableName: string
88+
): SelectBuilder<GenericResultWrapper, GenericResult, IsAsync> {
89+
return new SelectBuilder<GenericResultWrapper, GenericResult, IsAsync>(
10690
{
10791
tableName: tableName,
10892
},
@@ -117,9 +101,9 @@ export class QueryBuilder<GenericResultWrapper> {
117101

118102
fetchOne<GenericResult = DefaultReturnObject>(
119103
params: SelectOne
120-
): QueryWithExtra<GenericResultWrapper, OneResult<GenericResultWrapper, GenericResult>> {
104+
): QueryWithExtra<GenericResultWrapper, OneResult<GenericResultWrapper, GenericResult>, IsAsync> {
121105
return new QueryWithExtra(
122-
(q: Query) => {
106+
(q) => {
123107
return this.execute(q)
124108
},
125109
this._select({ ...params, limit: 1 }),
@@ -140,9 +124,9 @@ export class QueryBuilder<GenericResultWrapper> {
140124

141125
fetchAll<GenericResult = DefaultReturnObject>(
142126
params: SelectAll
143-
): QueryWithExtra<GenericResultWrapper, ArrayResult<GenericResultWrapper, GenericResult>> {
127+
): QueryWithExtra<GenericResultWrapper, ArrayResult<GenericResultWrapper, GenericResult>, IsAsync> {
144128
return new QueryWithExtra(
145-
(q: Query) => {
129+
(q) => {
146130
return this.execute(q)
147131
},
148132
this._select(params),
@@ -163,14 +147,14 @@ export class QueryBuilder<GenericResultWrapper> {
163147

164148
raw<GenericResult = DefaultReturnObject>(
165149
params: RawQueryFetchOne
166-
): Query<OneResult<GenericResultWrapper, GenericResult>>
150+
): Query<OneResult<GenericResultWrapper, GenericResult>, IsAsync>
167151
raw<GenericResult = DefaultReturnObject>(
168152
params: RawQueryFetchAll
169-
): Query<ArrayResult<GenericResultWrapper, GenericResult>>
170-
raw<GenericResult = DefaultReturnObject>(params: RawQueryWithoutFetching): Query<GenericResultWrapper>
153+
): Query<ArrayResult<GenericResultWrapper, GenericResult>, IsAsync>
154+
raw<GenericResult = DefaultReturnObject>(params: RawQueryWithoutFetching): Query<GenericResultWrapper, IsAsync>
171155
raw<GenericResult = DefaultReturnObject>(params: RawQuery): unknown {
172-
return new Query(
173-
(q: Query) => {
156+
return new Query<any, IsAsync>(
157+
(q) => {
174158
return this.execute(q)
175159
},
176160
params.query,
@@ -179,11 +163,13 @@ export class QueryBuilder<GenericResultWrapper> {
179163
)
180164
}
181165

182-
insert<GenericResult = DefaultReturnObject>(params: InsertOne): Query<OneResult<GenericResultWrapper, GenericResult>>
166+
insert<GenericResult = DefaultReturnObject>(
167+
params: InsertOne
168+
): Query<OneResult<GenericResultWrapper, GenericResult>, IsAsync>
183169
insert<GenericResult = DefaultReturnObject>(
184170
params: InsertMultiple
185-
): Query<ArrayResult<GenericResultWrapper, GenericResult>>
186-
insert<GenericResult = DefaultReturnObject>(params: InsertWithoutReturning): Query<GenericResultWrapper>
171+
): Query<ArrayResult<GenericResultWrapper, GenericResult>, IsAsync>
172+
insert<GenericResult = DefaultReturnObject>(params: InsertWithoutReturning): Query<GenericResultWrapper, IsAsync>
187173
insert<GenericResult = DefaultReturnObject>(params: Insert): unknown {
188174
let args: any[] = []
189175

@@ -214,8 +200,8 @@ export class QueryBuilder<GenericResultWrapper> {
214200

215201
const fetchType = Array.isArray(params.data) ? FetchTypes.ALL : FetchTypes.ONE
216202

217-
return new Query(
218-
(q: Query) => {
203+
return new Query<any, IsAsync>(
204+
(q) => {
219205
return this.execute(q)
220206
},
221207
this._insert(params),
@@ -226,8 +212,8 @@ export class QueryBuilder<GenericResultWrapper> {
226212

227213
update<GenericResult = DefaultReturnObject>(
228214
params: UpdateReturning
229-
): Query<ArrayResult<GenericResultWrapper, GenericResult>>
230-
update<GenericResult = DefaultReturnObject>(params: UpdateWithoutReturning): Query<GenericResultWrapper>
215+
): Query<ArrayResult<GenericResultWrapper, GenericResult>, IsAsync>
216+
update<GenericResult = DefaultReturnObject>(params: UpdateWithoutReturning): Query<GenericResultWrapper, IsAsync>
231217
update<GenericResult = DefaultReturnObject>(params: Update): unknown {
232218
let args = this._parse_arguments(params.data)
233219

@@ -239,8 +225,8 @@ export class QueryBuilder<GenericResultWrapper> {
239225
}
240226
}
241227

242-
return new Query(
243-
(q: Query) => {
228+
return new Query<any, IsAsync>(
229+
(q) => {
244230
return this.execute(q)
245231
},
246232
this._update(params),
@@ -251,11 +237,11 @@ export class QueryBuilder<GenericResultWrapper> {
251237

252238
delete<GenericResult = DefaultReturnObject>(
253239
params: DeleteReturning
254-
): Query<ArrayResult<GenericResultWrapper, GenericResult>>
255-
delete<GenericResult = DefaultReturnObject>(params: DeleteWithoutReturning): Query<GenericResultWrapper>
240+
): Query<ArrayResult<GenericResultWrapper, GenericResult>, IsAsync>
241+
delete<GenericResult = DefaultReturnObject>(params: DeleteWithoutReturning): Query<GenericResultWrapper, IsAsync>
256242
delete<GenericResult = DefaultReturnObject>(params: Delete): unknown {
257-
return new Query(
258-
(q: Query) => {
243+
return new Query<any, IsAsync>(
244+
(q) => {
259245
return this.execute(q)
260246
},
261247
this._delete(params),

src/databases/d1.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class D1QB extends QueryBuilder<D1Result> {
1212
}
1313

1414
async execute(query: Query) {
15-
return await this.loggerWrapper(query, async () => {
15+
return await this.loggerWrapper(query, this.options.logger, async () => {
1616
let stmt = this.db.prepare(query.query)
1717

1818
if (query.arguments) {
@@ -38,7 +38,7 @@ export class D1QB extends QueryBuilder<D1Result> {
3838
}
3939

4040
async batchExecute(queryArray: Query[]) {
41-
return await this.loggerWrapper(queryArray, async () => {
41+
return await this.loggerWrapper(queryArray, this.options.logger, async () => {
4242
const statements = queryArray.map((query) => {
4343
let stmt = this.db.prepare(query.query)
4444
if (query.arguments) {

src/databases/do.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ import { QueryBuilder } from '../builder'
22
import { Query } from '../tools'
33
import { FetchTypes } from '../enums'
44
import { QueryBuilderOptions } from '../interfaces'
5+
import { syncLoggerWrapper } from '../logger'
56

6-
export class DOQB extends QueryBuilder<{}> {
7+
export class DOQB extends QueryBuilder<{}, false> {
78
public db: any
9+
loggerWrapper = syncLoggerWrapper
810

9-
constructor(db: any, options?: QueryBuilderOptions) {
11+
constructor(db: any, options?: QueryBuilderOptions<false>) {
1012
super(options)
1113
this.db = db
1214
}
1315

14-
async execute(query: Query) {
15-
return await this.loggerWrapper(query, async () => {
16+
execute(query: Query<any, false>) {
17+
return this.loggerWrapper(query, this.options.logger, () => {
1618
if (query.arguments) {
1719
let stmt = this.db.prepare(query.query)
1820
// @ts-expect-error Their types appear to be wrong here

src/databases/pg.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class PGQB extends QueryBuilder<PGResult> {
2020
}
2121

2222
async execute(query: Query) {
23-
return await this.loggerWrapper(query, async () => {
23+
return await this.loggerWrapper(query, this.options.logger, async () => {
2424
const queryString = query.query.replaceAll('?', '$')
2525

2626
let result

src/interfaces.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ export type QueryLoggerMeta = {
88
duration?: number
99
}
1010

11-
export type QueryBuilderOptions = {
12-
logger?: (query: RawQuery, meta: QueryLoggerMeta) => void | Promise<void>
11+
export type QueryBuilderOptions<IsAsync extends boolean = true> = {
12+
logger?: (query: RawQuery, meta: QueryLoggerMeta) => MaybeAsync<IsAsync, void>
1313
}
1414

1515
export type DefaultObject = Record<string, Primitive>
@@ -145,3 +145,7 @@ export type ArrayResult<ResultWrapper, Result> = Merge<ResultWrapper, { results?
145145
export type OneResult<ResultWrapper, Result> = Merge<ResultWrapper, { results?: Result }>
146146

147147
export type CountResult<GenericResultWrapper> = OneResult<GenericResultWrapper, { total: number }>
148+
149+
export type AsyncType<T> = Promise<T>
150+
export type SyncType<T> = T
151+
export type MaybeAsync<IsAsync extends boolean, T> = IsAsync extends true ? AsyncType<T> : SyncType<T>

0 commit comments

Comments
 (0)