@d-mok/quasar-app-extension-quasar-axe 3.1.68 → 3.1.71
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/package.json +11 -11
- package/src/templates/src/utils/index.ts +1 -1
- package/src/templates/src/utils/puppets/index.ts +206 -119
- package/src/templates/src/utils/puppets/test.ts +7 -5
- package/src/templates/src/utils/storage.ts +10 -10
- package/src/templates/src/utils/supabase.ts +3 -70
- package/src/templates/src/boot/axe/components/QxText.vue +0 -34
- package/src/templates/src/utils/puppets/builder/index.ts +0 -235
- package/src/templates/src/utils/puppets/builder/ui.ts +0 -31
- package/src/templates/src/utils/puppets/core/db.ts +0 -122
- package/src/templates/src/utils/puppets/core/index.ts +0 -44
- package/src/templates/src/utils/puppets/type.ts +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d-mok/quasar-app-extension-quasar-axe",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.71",
|
|
4
4
|
"description": "A Quasar App Extension",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,25 +8,25 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@handsontable/vue3": "^15.2.0",
|
|
11
|
-
"@supabase/supabase-js": "^2.
|
|
11
|
+
"@supabase/supabase-js": "^2.89.0",
|
|
12
12
|
"@types/lodash": "^4.17.20",
|
|
13
13
|
"@types/webpack-env": "^1.18.8",
|
|
14
|
-
"@vueuse/core": "^
|
|
15
|
-
"@vueuse/integrations": "^
|
|
16
|
-
"@vueuse/router": "^
|
|
14
|
+
"@vueuse/core": "^14.1.0",
|
|
15
|
+
"@vueuse/integrations": "^14.1.0",
|
|
16
|
+
"@vueuse/router": "^14.1.0",
|
|
17
17
|
"handsontable": "^15.2.0",
|
|
18
18
|
"jszip": "^3.10.1",
|
|
19
19
|
"lodash": "^4.17.21",
|
|
20
|
-
"sapphire-js": "^2.1.
|
|
20
|
+
"sapphire-js": "^2.1.50",
|
|
21
21
|
"sortablejs": "^1.15.6",
|
|
22
|
-
"valibot": "^1.
|
|
22
|
+
"valibot": "^1.2.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@quasar/app-vite": "^2.
|
|
25
|
+
"@quasar/app-vite": "^2.4.0",
|
|
26
26
|
"@quasar/extras": "^1.17.0 ",
|
|
27
|
-
"@types/node": "^
|
|
28
|
-
"core-js": "^3.
|
|
29
|
-
"quasar": "^2.18.
|
|
27
|
+
"@types/node": "^25.0.3",
|
|
28
|
+
"core-js": "^3.47.0",
|
|
29
|
+
"quasar": "^2.18.6",
|
|
30
30
|
"typescript": "^5.9.2",
|
|
31
31
|
"vue": "^3.5.18"
|
|
32
32
|
},
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DeleteBuilder,
|
|
3
|
-
InsertBuilder,
|
|
4
|
-
SelectBuilder,
|
|
5
|
-
SelectInBuilder,
|
|
6
|
-
SelectRPCBuilder,
|
|
7
|
-
UpdateBuilder,
|
|
8
|
-
UpsertBuilder,
|
|
9
|
-
} from './builder'
|
|
10
|
-
import { Constructor, Key, BooleanKeys } from './type'
|
|
11
|
-
import { Core } from './core'
|
|
12
1
|
import { reactive } from 'vue'
|
|
2
|
+
import { supabase, handleError } from '../supabase'
|
|
3
|
+
import { qDialog, qNotify } from '..'
|
|
4
|
+
import { LoadingBar } from 'quasar'
|
|
5
|
+
|
|
6
|
+
import type { PostgrestFilterBuilder } from '@supabase/postgrest-js'
|
|
7
|
+
|
|
8
|
+
type Constructor<T extends object = {}> = new (...args: any[]) => T
|
|
9
|
+
|
|
10
|
+
type BooleanKeys<T> = {
|
|
11
|
+
[k in keyof T]: T[k] extends boolean ? k : never
|
|
12
|
+
}[keyof T]
|
|
13
|
+
// type OnlyBoolean<T> = { [k in BooleanKeys<T>]: boolean }
|
|
14
|
+
|
|
15
|
+
type Key<R> = string & keyof R
|
|
13
16
|
|
|
14
17
|
export function ORM<
|
|
15
18
|
RBase extends Constructor,
|
|
16
|
-
|
|
17
|
-
>(RClass: RBase, tableName: string, idKey:
|
|
19
|
+
IdK extends Key<InstanceType<RBase>>
|
|
20
|
+
>(RClass: RBase, tableName: string, idKey: IdK) {
|
|
18
21
|
type R = InstanceType<RBase>
|
|
19
22
|
|
|
20
23
|
class E extends RClass {
|
|
@@ -27,18 +30,26 @@ export function ORM<
|
|
|
27
30
|
this._hostTable = hostTable
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
update(
|
|
31
|
-
|
|
33
|
+
async update(
|
|
34
|
+
this: E & R,
|
|
35
|
+
row: Partial<R>,
|
|
36
|
+
options?: uiOptions
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
this._hostTable!.update(this[idKey], row, options)
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
delete(this: E & R) {
|
|
35
|
-
|
|
41
|
+
async delete(this: E & R, options?: uiOptions): Promise<void> {
|
|
42
|
+
this._hostTable!.delete(this[idKey], options)
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
toggle(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
async toggle(
|
|
46
|
+
this: E & R,
|
|
47
|
+
key: BooleanKeys<R>,
|
|
48
|
+
options?: uiOptions
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
const val = !this[key]
|
|
51
|
+
const obj = { [key]: val } as Partial<R>
|
|
52
|
+
this._hostTable!.update(this[idKey], obj, options)
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
toPlain(): R {
|
|
@@ -51,21 +62,11 @@ export function ORM<
|
|
|
51
62
|
function getTable<EBase extends RBase>(EClass: EBase) {
|
|
52
63
|
type T = InstanceType<EBase>
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
return ($: R) => new EClass($, host) as T
|
|
56
|
-
}
|
|
65
|
+
const fields = Object.keys(new RClass()).join(',') as '*'
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
return new Core<T, R>(
|
|
60
|
-
tableName,
|
|
61
|
-
idKey,
|
|
62
|
-
Object.keys(new RClass()) as Key<R>[],
|
|
63
|
-
convertor(host)
|
|
64
|
-
)
|
|
65
|
-
}
|
|
67
|
+
type Filter = PostgrestFilterBuilder<any, any, R, any>
|
|
66
68
|
|
|
67
69
|
class TableClass extends Array<InstanceType<EBase>> {
|
|
68
|
-
/** make reactive */
|
|
69
70
|
reactive(): this {
|
|
70
71
|
const rec = reactive(this) as this
|
|
71
72
|
if (process.env.DEBUGGING) {
|
|
@@ -77,130 +78,205 @@ export function ORM<
|
|
|
77
78
|
|
|
78
79
|
protected onChange(): void {}
|
|
79
80
|
protected onEdit(): void {}
|
|
80
|
-
protected onSanitize(row:
|
|
81
|
+
protected onSanitize<P extends Partial<R>>(row: P): P {
|
|
81
82
|
return row
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.set($)
|
|
87
|
-
this.onChange()
|
|
88
|
-
}
|
|
89
|
-
return new SelectBuilder(core(this), callback)
|
|
85
|
+
private convert(R: R): T {
|
|
86
|
+
return new EClass(R, this) as T
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.absorb($, idKey)
|
|
95
|
-
this.onChange()
|
|
96
|
-
}
|
|
97
|
-
return new SelectBuilder(core(this), callback)
|
|
89
|
+
private convertAll(Rs: R[]): T[] {
|
|
90
|
+
return Rs.map(r => this.convert(r))
|
|
98
91
|
}
|
|
99
92
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
private async wrapper<X>(
|
|
94
|
+
logType: 'select' | 'insert' | 'update' | 'delete',
|
|
95
|
+
options: uiOptions | undefined,
|
|
96
|
+
fn: () => Promise<X>
|
|
97
|
+
): Promise<X> {
|
|
98
|
+
const { confirm, notify, loading } = options ?? {}
|
|
99
|
+
|
|
100
|
+
if (confirm) {
|
|
101
|
+
await qDialog.confirm(...confirm)
|
|
105
102
|
}
|
|
106
|
-
return new SelectInBuilder(key, vals, core(this), callback)
|
|
107
|
-
}
|
|
108
103
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
this.absorb($, idKey)
|
|
112
|
-
this.sortBy($ => vals.indexOf($[key]))
|
|
113
|
-
this.onChange()
|
|
104
|
+
if (loading === false) {
|
|
105
|
+
LoadingBar.setDefaults({ size: '0px' })
|
|
114
106
|
}
|
|
115
|
-
|
|
107
|
+
|
|
108
|
+
const entities = await fn()
|
|
109
|
+
|
|
110
|
+
if (loading === false) {
|
|
111
|
+
setTimeout(
|
|
112
|
+
() => LoadingBar.setDefaults({ size: '5px' }),
|
|
113
|
+
3000
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const n =
|
|
118
|
+
notify ??
|
|
119
|
+
{
|
|
120
|
+
select: false as const,
|
|
121
|
+
insert: 'Created!',
|
|
122
|
+
update: 'Updated!',
|
|
123
|
+
delete: 'Deleted!',
|
|
124
|
+
}[logType]
|
|
125
|
+
|
|
126
|
+
if (n !== false) {
|
|
127
|
+
qNotify.toast(n)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.dev(`[${tableName}] ${logType}`, entities)
|
|
131
|
+
|
|
132
|
+
return entities
|
|
116
133
|
}
|
|
117
134
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
async read(query: (q: Filter) => Filter = q => q): Promise<R[]> {
|
|
136
|
+
const q = supabase.from(tableName).select(fields)
|
|
137
|
+
return await query(q).then(handleError)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async select(
|
|
141
|
+
query: (q: Filter) => Filter = q => q,
|
|
142
|
+
options?: selectOptions
|
|
143
|
+
): Promise<void> {
|
|
144
|
+
await this.wrapper('select', options, async () => {
|
|
145
|
+
const data = await this.read(query)
|
|
146
|
+
const entities = this.convertAll(data)
|
|
147
|
+
|
|
148
|
+
options?.patch === true
|
|
149
|
+
? this.absorb(entities, idKey)
|
|
150
|
+
: this.set(entities)
|
|
151
|
+
|
|
121
152
|
this.onChange()
|
|
122
|
-
}
|
|
123
|
-
return new SelectRPCBuilder(param, core(this), callback)
|
|
153
|
+
})
|
|
124
154
|
}
|
|
125
155
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
156
|
+
async selectIn<K extends Key<R>>(
|
|
157
|
+
key: K,
|
|
158
|
+
vals: R[K][],
|
|
159
|
+
options?: uiOptions & { patch?: true }
|
|
160
|
+
): Promise<void> {
|
|
161
|
+
await this.wrapper('select', options, async () => {
|
|
162
|
+
const promises = vals.chunk(100).map(async chunkPiece => {
|
|
163
|
+
return await supabase
|
|
164
|
+
.from(tableName)
|
|
165
|
+
.select(fields)
|
|
166
|
+
.in(key, chunkPiece as any)
|
|
167
|
+
.then(handleError)
|
|
168
|
+
})
|
|
169
|
+
const data: R[] = (await Promise.all(promises)).flat()
|
|
170
|
+
|
|
171
|
+
const entities = this.convertAll(data)
|
|
172
|
+
|
|
173
|
+
options?.patch === true
|
|
174
|
+
? this.absorb(entities, idKey)
|
|
175
|
+
: this.set(entities)
|
|
176
|
+
|
|
177
|
+
this.sortBy($ => vals.indexOf($[key]))
|
|
129
178
|
this.onChange()
|
|
130
|
-
}
|
|
131
|
-
return new SelectRPCBuilder(param, core(this), callback)
|
|
179
|
+
})
|
|
132
180
|
}
|
|
133
181
|
|
|
134
|
-
selectCustom(rows: R[]) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
182
|
+
selectCustom(rows: R[], options?: { patch?: true }): void {
|
|
183
|
+
const entities = this.convertAll(rows)
|
|
184
|
+
options?.patch === true
|
|
185
|
+
? this.absorb(entities, idKey)
|
|
186
|
+
: this.set(entities)
|
|
138
187
|
this.onChange()
|
|
139
188
|
}
|
|
140
189
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
190
|
+
async insertMany(
|
|
191
|
+
rows: Partial<R>[],
|
|
192
|
+
options?: uiOptions
|
|
193
|
+
): Promise<T[]> {
|
|
194
|
+
return await this.wrapper('insert', options, async () => {
|
|
195
|
+
const data: R[] = await supabase
|
|
196
|
+
.from(tableName)
|
|
197
|
+
.insert(rows.map($ => this.onSanitize($)))
|
|
198
|
+
.select(fields)
|
|
199
|
+
.then(handleError)
|
|
200
|
+
|
|
201
|
+
const entities = this.convertAll(data)
|
|
202
|
+
|
|
203
|
+
this.push(...entities)
|
|
145
204
|
this.onChange()
|
|
146
205
|
this.onEdit()
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
206
|
+
|
|
207
|
+
return entities
|
|
208
|
+
})
|
|
151
209
|
}
|
|
152
210
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this.push(...$)
|
|
157
|
-
this.onChange()
|
|
158
|
-
this.onEdit()
|
|
159
|
-
}
|
|
160
|
-
return new InsertBuilder(rows, core(this), callback).notify(
|
|
161
|
-
'Created!'
|
|
162
|
-
)
|
|
211
|
+
async insert(row: Partial<R>, options?: uiOptions): Promise<T> {
|
|
212
|
+
let entities = await this.insertMany([row], options)
|
|
213
|
+
return entities[0]!
|
|
163
214
|
}
|
|
164
215
|
|
|
165
|
-
update(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
216
|
+
async update(
|
|
217
|
+
idVal: R[IdK],
|
|
218
|
+
row: Partial<R>,
|
|
219
|
+
options?: uiOptions
|
|
220
|
+
): Promise<T> {
|
|
221
|
+
return await this.wrapper('update', options, async () => {
|
|
222
|
+
const data: R = await supabase
|
|
223
|
+
.from(tableName)
|
|
224
|
+
.update(this.onSanitize(row))
|
|
225
|
+
.eq(idKey, idVal as any)
|
|
226
|
+
.select(fields)
|
|
227
|
+
.single()
|
|
228
|
+
.then(handleError)
|
|
229
|
+
|
|
230
|
+
const entity = this.convert(data)
|
|
231
|
+
|
|
232
|
+
this.merge([entity], idKey)
|
|
169
233
|
this.onChange()
|
|
170
234
|
this.onEdit()
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
row,
|
|
175
|
-
core(this),
|
|
176
|
-
callback
|
|
177
|
-
).notify('Updated!')
|
|
235
|
+
|
|
236
|
+
return entity
|
|
237
|
+
})
|
|
178
238
|
}
|
|
179
239
|
|
|
180
|
-
delete(idVal: R[
|
|
181
|
-
|
|
240
|
+
async delete(idVal: R[IdK], options?: uiOptions): Promise<void> {
|
|
241
|
+
await this.wrapper('delete', options, async () => {
|
|
242
|
+
await supabase
|
|
243
|
+
.from(tableName)
|
|
244
|
+
.delete()
|
|
245
|
+
.eq(idKey, idVal as any)
|
|
246
|
+
.single()
|
|
247
|
+
.then(handleError)
|
|
248
|
+
|
|
182
249
|
this.remove($ => $[idKey] === idVal)
|
|
183
250
|
this.onChange()
|
|
184
251
|
this.onEdit()
|
|
185
|
-
}
|
|
186
|
-
return new DeleteBuilder(idVal, core(this), callback).notify(
|
|
187
|
-
'Deleted!'
|
|
188
|
-
)
|
|
252
|
+
})
|
|
189
253
|
}
|
|
190
254
|
|
|
191
|
-
upsert
|
|
255
|
+
async upsert<P extends Partial<R>>(
|
|
256
|
+
row: P,
|
|
257
|
+
conflictKeys: Key<P>[],
|
|
258
|
+
options?: uiOptions
|
|
259
|
+
): Promise<T> {
|
|
192
260
|
row = this.onSanitize(row)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
261
|
+
|
|
262
|
+
let found: R[] = await this.read(q =>
|
|
263
|
+
q.match(Object.pick(row, conflictKeys))
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
if (found.length > 1) {
|
|
267
|
+
await qDialog.error(
|
|
268
|
+
'Fail to upsert',
|
|
269
|
+
'More than 1 rows matching the conflictKeys are found!'
|
|
270
|
+
)
|
|
271
|
+
throw 'Fail to upsert! More than 1 rows matching the conflictKeys are found!'
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (found[0]) {
|
|
275
|
+
let id = found[0][idKey]
|
|
276
|
+
return await this.update(id, row, options)
|
|
277
|
+
} else {
|
|
278
|
+
return await this.insert(row, options)
|
|
197
279
|
}
|
|
198
|
-
return new UpsertBuilder(
|
|
199
|
-
row,
|
|
200
|
-
conflictKeys,
|
|
201
|
-
core(this),
|
|
202
|
-
callback
|
|
203
|
-
).notify('Updated!')
|
|
204
280
|
}
|
|
205
281
|
}
|
|
206
282
|
|
|
@@ -218,3 +294,14 @@ if (process.env.DEBUGGING) {
|
|
|
218
294
|
// @ts-ignore
|
|
219
295
|
globalThis.TABLES = []
|
|
220
296
|
}
|
|
297
|
+
|
|
298
|
+
type uiOptions = {
|
|
299
|
+
confirm?: [string, string]
|
|
300
|
+
notify?: string | false
|
|
301
|
+
loading?: boolean
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
type selectOptions = {
|
|
305
|
+
loading?: boolean
|
|
306
|
+
patch?: true
|
|
307
|
+
}
|
|
@@ -55,21 +55,23 @@ class StudentTable extends Table(Student) {
|
|
|
55
55
|
|
|
56
56
|
let ss = new StudentTable()
|
|
57
57
|
|
|
58
|
-
ss.
|
|
58
|
+
let x1 = await ss.read()
|
|
59
|
+
|
|
60
|
+
ss.select(q => q.eq('name', 'a').eq('num', 3).limit(100).order('class'))
|
|
59
61
|
// @ts-expect-error
|
|
60
|
-
ss.select(
|
|
62
|
+
ss.select(q => q.eq('isPrefect', 'a'))
|
|
61
63
|
ss.selectIn('num', [1, 2, 3])
|
|
62
64
|
// @ts-expect-error
|
|
63
65
|
ss.selectIn('name', [1, 2, 3])
|
|
64
66
|
// @ts-expect-error
|
|
65
67
|
ss.selectIn('full', [1, 2, 3])
|
|
66
|
-
ss.insert({ name: 'a' })
|
|
68
|
+
let x2 = await ss.insert({ name: 'a' })
|
|
67
69
|
// @ts-expect-error
|
|
68
70
|
ss.insert({ name: 0 })
|
|
69
71
|
ss.delete('')
|
|
70
72
|
// @ts-expect-error
|
|
71
73
|
ss.delete(1)
|
|
72
|
-
ss.update('', { name: 'a' })
|
|
74
|
+
let x3 = await ss.update('', { name: 'a' })
|
|
73
75
|
// @ts-expect-error
|
|
74
76
|
ss.update('', { name: 0 })
|
|
75
77
|
// @ts-expect-error
|
|
@@ -77,6 +79,6 @@ ss.update('', { full: 'a' })
|
|
|
77
79
|
// @ts-expect-error
|
|
78
80
|
ss.update(0, { name: 'a' })
|
|
79
81
|
|
|
80
|
-
let st = ss[0]
|
|
82
|
+
let st = ss[0]!
|
|
81
83
|
st.name
|
|
82
84
|
st.full
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { supabase,
|
|
1
|
+
import { supabase, handleError } from '.'
|
|
2
2
|
import { reactive } from 'vue'
|
|
3
3
|
import { FileOptions } from '@supabase/storage-js'
|
|
4
4
|
import { mimeTypesToExt } from './mimeTypesToExt'
|
|
@@ -13,7 +13,7 @@ export class SupabaseBucket extends Map<string, string> {
|
|
|
13
13
|
const data = await supabase.storage
|
|
14
14
|
.from(this.bucketName)
|
|
15
15
|
.list(undefined, { limit: 10000 })
|
|
16
|
-
.then(
|
|
16
|
+
.then(handleError)
|
|
17
17
|
return data.pluck('name')
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@ export class SupabaseBucket extends Map<string, string> {
|
|
|
21
21
|
const data = await supabase.storage
|
|
22
22
|
.from(this.bucketName)
|
|
23
23
|
.createSignedUrl(path, 60 * 60 * 24)
|
|
24
|
-
.then(
|
|
24
|
+
.then(handleError)
|
|
25
25
|
return data.signedUrl
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -31,7 +31,7 @@ export class SupabaseBucket extends Map<string, string> {
|
|
|
31
31
|
const data = await supabase.storage
|
|
32
32
|
.from(this.bucketName)
|
|
33
33
|
.createSignedUrls(paths, 60 * 60 * 24)
|
|
34
|
-
.then(
|
|
34
|
+
.then(handleError)
|
|
35
35
|
return data
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -58,7 +58,7 @@ export class SupabaseBucket extends Map<string, string> {
|
|
|
58
58
|
const data = await supabase.storage
|
|
59
59
|
.from(this.bucketName)
|
|
60
60
|
.upload(path, file, fileOptions)
|
|
61
|
-
.then(
|
|
61
|
+
.then(handleError)
|
|
62
62
|
await this.load(path)
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -70,7 +70,7 @@ export class SupabaseBucket extends Map<string, string> {
|
|
|
70
70
|
const data = await supabase.storage
|
|
71
71
|
.from(this.bucketName)
|
|
72
72
|
.update(path, file, fileOptions)
|
|
73
|
-
.then(
|
|
73
|
+
.then(handleError)
|
|
74
74
|
await this.load(path)
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -82,7 +82,7 @@ export class SupabaseBucket extends Map<string, string> {
|
|
|
82
82
|
const data = await supabase.storage
|
|
83
83
|
.from(this.bucketName)
|
|
84
84
|
.upload(path, file, { ...fileOptions, upsert: true })
|
|
85
|
-
.then(
|
|
85
|
+
.then(handleError)
|
|
86
86
|
await this.load(path)
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -90,7 +90,7 @@ export class SupabaseBucket extends Map<string, string> {
|
|
|
90
90
|
const data = await supabase.storage
|
|
91
91
|
.from(this.bucketName)
|
|
92
92
|
.remove([path])
|
|
93
|
-
.then(
|
|
93
|
+
.then(handleError)
|
|
94
94
|
this.delete(path)
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -98,7 +98,7 @@ export class SupabaseBucket extends Map<string, string> {
|
|
|
98
98
|
const data = await supabase.storage
|
|
99
99
|
.from(this.bucketName)
|
|
100
100
|
.copy(fromPath, toPath)
|
|
101
|
-
.then(
|
|
101
|
+
.then(handleError)
|
|
102
102
|
await this.load(toPath)
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -106,7 +106,7 @@ export class SupabaseBucket extends Map<string, string> {
|
|
|
106
106
|
const data = await supabase.storage
|
|
107
107
|
.from(this.bucketName)
|
|
108
108
|
.move(fromPath, toPath)
|
|
109
|
-
.then(
|
|
109
|
+
.then(handleError)
|
|
110
110
|
await this.load(toPath)
|
|
111
111
|
this.delete(fromPath)
|
|
112
112
|
}
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createClient,
|
|
3
|
-
PostgrestError,
|
|
4
|
-
SupabaseClient,
|
|
5
|
-
Session,
|
|
6
|
-
} from '@supabase/supabase-js'
|
|
1
|
+
import { PostgrestError, SupabaseClient } from '@supabase/supabase-js'
|
|
7
2
|
import { qDialog } from './dialog'
|
|
8
3
|
|
|
9
|
-
export type {
|
|
10
|
-
PostgrestFilterBuilder,
|
|
11
|
-
PostgrestSingleResponse,
|
|
12
|
-
} from '@supabase/postgrest-js'
|
|
13
|
-
|
|
14
4
|
let SUPABASE_URL = process.env.SUPABASE_URL ?? ''
|
|
15
5
|
let SUPABASE_KEY = process.env.SUPABASE_KEY ?? ''
|
|
16
6
|
|
|
17
7
|
export class MySupabaseClient extends SupabaseClient {
|
|
18
8
|
email: string = 'unauthenticated'
|
|
19
|
-
// session: Session | null = null
|
|
20
9
|
|
|
21
10
|
constructor() {
|
|
22
11
|
super(SUPABASE_URL, SUPABASE_KEY, {
|
|
@@ -24,15 +13,9 @@ export class MySupabaseClient extends SupabaseClient {
|
|
|
24
13
|
// @ts-ignore
|
|
25
14
|
schema: process.env.SUPABASE_SCHEMA ?? 'public',
|
|
26
15
|
},
|
|
27
|
-
// auth: {
|
|
28
|
-
// autoRefreshToken: true,
|
|
29
|
-
// persistSession: true,
|
|
30
|
-
// detectSessionInUrl: true,
|
|
31
|
-
// },
|
|
32
16
|
})
|
|
33
17
|
this.auth.onAuthStateChange((event, session) => {
|
|
34
18
|
console.log('[SUPABASE AUTH]', event, session?.user?.email)
|
|
35
|
-
// this.session = session
|
|
36
19
|
this.email = session?.user?.email ?? 'unauthenticated'
|
|
37
20
|
})
|
|
38
21
|
}
|
|
@@ -51,41 +34,10 @@ export class MySupabaseClient extends SupabaseClient {
|
|
|
51
34
|
})
|
|
52
35
|
}
|
|
53
36
|
|
|
54
|
-
async call<T>(fn: string, params?: object | undefined): Promise<T[]> {
|
|
55
|
-
return await this.rpc<string, any>(fn, params).then(handleThrow)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async callSingle<T>(fn: string, params?: object | undefined): Promise<T> {
|
|
59
|
-
return (await this.rpc<string, any>(fn, params)
|
|
60
|
-
.single()
|
|
61
|
-
.then(handleThrow)) as T
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// private async tryRefreshSession() {
|
|
65
|
-
// try {
|
|
66
|
-
// await this.auth.refreshSession()
|
|
67
|
-
// } catch (e) {
|
|
68
|
-
// console.error('[Supabase] Refresh session failed', e)
|
|
69
|
-
// }
|
|
70
|
-
// }
|
|
71
|
-
|
|
72
37
|
async waitForSignin(): Promise<void> {
|
|
73
|
-
// await this.tryRefreshSession()
|
|
74
38
|
await this.auth.initialize()
|
|
75
39
|
const { data } = await this.auth.getClaims()
|
|
76
40
|
if (data === null) this.signIn()
|
|
77
|
-
// setInterval(async () => {
|
|
78
|
-
// if (this.session === null) await this.tryRefreshSession()
|
|
79
|
-
// if (this.session === null) this.signIn()
|
|
80
|
-
// }, 1000)
|
|
81
|
-
// setInterval(async () => {
|
|
82
|
-
// await this.auth.refreshSession()
|
|
83
|
-
// }, 1000 * 60 * 30 * 0.99)
|
|
84
|
-
// await waitFor(
|
|
85
|
-
// () => this.session !== null,
|
|
86
|
-
// '[Wait for SignIn] waiting...',
|
|
87
|
-
// '[Wait for SignIn] DONE!'
|
|
88
|
-
// )
|
|
89
41
|
}
|
|
90
42
|
}
|
|
91
43
|
|
|
@@ -93,7 +45,7 @@ export let supabase: MySupabaseClient
|
|
|
93
45
|
if (SUPABASE_URL !== '' && SUPABASE_KEY !== '')
|
|
94
46
|
supabase = new MySupabaseClient()
|
|
95
47
|
|
|
96
|
-
|
|
48
|
+
function HANDLE_ERROR<T>(
|
|
97
49
|
data: T[] | T | null,
|
|
98
50
|
error: PostgrestError | Error | null
|
|
99
51
|
): asserts data {
|
|
@@ -119,7 +71,7 @@ export function HANDLE_ERROR<T>(
|
|
|
119
71
|
}
|
|
120
72
|
}
|
|
121
73
|
|
|
122
|
-
export function
|
|
74
|
+
export function handleError<D>({
|
|
123
75
|
data,
|
|
124
76
|
error,
|
|
125
77
|
}: {
|
|
@@ -129,22 +81,3 @@ export function handleThrow<D>({
|
|
|
129
81
|
HANDLE_ERROR(data, error)
|
|
130
82
|
return data
|
|
131
83
|
}
|
|
132
|
-
|
|
133
|
-
// async function waitFor(
|
|
134
|
-
// predicate: () => boolean,
|
|
135
|
-
// waitingMsg: string,
|
|
136
|
-
// doneMsg: string
|
|
137
|
-
// ) {
|
|
138
|
-
// return new Promise(resolve => {
|
|
139
|
-
// async function checker() {
|
|
140
|
-
// if (predicate()) {
|
|
141
|
-
// console.log(doneMsg)
|
|
142
|
-
// resolve(true)
|
|
143
|
-
// } else {
|
|
144
|
-
// console.log(waitingMsg)
|
|
145
|
-
// setTimeout(checker, 50)
|
|
146
|
-
// }
|
|
147
|
-
// }
|
|
148
|
-
// checker()
|
|
149
|
-
// })
|
|
150
|
-
// }
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
v-bind="$attrs"
|
|
4
|
-
:class="{
|
|
5
|
-
'text-caption': caption,
|
|
6
|
-
'text-subtitle1': subtitle1,
|
|
7
|
-
'text-subtitle2': subtitle2,
|
|
8
|
-
'text-body1': body1,
|
|
9
|
-
'text-body2': body2,
|
|
10
|
-
'text-h6': h6,
|
|
11
|
-
'text-h5': h5,
|
|
12
|
-
'text-h4': h4,
|
|
13
|
-
'text-weight-bold': bold,
|
|
14
|
-
'text-weight-bolder': bolder,
|
|
15
|
-
}"
|
|
16
|
-
>
|
|
17
|
-
<slot></slot>
|
|
18
|
-
</div>
|
|
19
|
-
</template>
|
|
20
|
-
|
|
21
|
-
<script lang="ts" setup>
|
|
22
|
-
const props = defineProps<{
|
|
23
|
-
bold?: boolean
|
|
24
|
-
bolder?: boolean
|
|
25
|
-
caption?: boolean
|
|
26
|
-
subtitle1?: boolean
|
|
27
|
-
subtitle2?: boolean
|
|
28
|
-
body1?: boolean
|
|
29
|
-
body2?: boolean
|
|
30
|
-
h6?: boolean
|
|
31
|
-
h5?: boolean
|
|
32
|
-
h4?: boolean
|
|
33
|
-
}>()
|
|
34
|
-
</script>
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { Core } from '../core'
|
|
2
|
-
import { Filter, Key } from '../type'
|
|
3
|
-
import { ui } from './ui'
|
|
4
|
-
|
|
5
|
-
type Promisable<T> = T | PromiseLike<T>
|
|
6
|
-
|
|
7
|
-
type onfulfilled<T, U> = ((value: T) => Promisable<U>) | undefined | null
|
|
8
|
-
|
|
9
|
-
type onrejected<U> = ((reason: any) => Promisable<U>) | undefined | null
|
|
10
|
-
|
|
11
|
-
abstract class BuilderUI {
|
|
12
|
-
private isSilence: boolean = false
|
|
13
|
-
private isConfirm: boolean = false
|
|
14
|
-
private confirmContent: { title: string; msg: string } = {
|
|
15
|
-
title: '',
|
|
16
|
-
msg: '',
|
|
17
|
-
}
|
|
18
|
-
private isNotify: boolean = false
|
|
19
|
-
private notifyContent: string = ''
|
|
20
|
-
|
|
21
|
-
protected abstract dataLogic(): Promise<void>
|
|
22
|
-
|
|
23
|
-
protected async run() {
|
|
24
|
-
if (this.isSilence) ui.loadingOff()
|
|
25
|
-
|
|
26
|
-
if (this.isConfirm) {
|
|
27
|
-
let { title, msg } = this.confirmContent
|
|
28
|
-
await ui.onConfirm(title, msg)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
await this.dataLogic()
|
|
32
|
-
|
|
33
|
-
if (this.isNotify) ui.onNotify(this.notifyContent)
|
|
34
|
-
|
|
35
|
-
if (this.isSilence) ui.loadingOn()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
confirm(title: string, ...msgs: string[]): this {
|
|
39
|
-
this.isConfirm = true
|
|
40
|
-
this.confirmContent = { title, msg: msgs.join('<br/>') }
|
|
41
|
-
return this
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
notify(msg: string): this {
|
|
45
|
-
this.isNotify = true
|
|
46
|
-
this.notifyContent = msg
|
|
47
|
-
return this
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
mute(): this {
|
|
51
|
-
this.isNotify = false
|
|
52
|
-
return this
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @deprecated
|
|
57
|
-
*/
|
|
58
|
-
slience(): this {
|
|
59
|
-
this.isSilence = true
|
|
60
|
-
this.isNotify = false
|
|
61
|
-
return this
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
silence(): this {
|
|
65
|
-
this.isSilence = true
|
|
66
|
-
this.isNotify = false
|
|
67
|
-
return this
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
abstract class Builder<T, R> extends BuilderUI implements PromiseLike<void> {
|
|
72
|
-
constructor(
|
|
73
|
-
protected core: Core<T, R>,
|
|
74
|
-
private callback: (objs: T[]) => void
|
|
75
|
-
) {
|
|
76
|
-
super()
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
protected abstract logType: string
|
|
80
|
-
|
|
81
|
-
protected abstract fetch(): Promise<R[]>
|
|
82
|
-
|
|
83
|
-
protected override async dataLogic(): Promise<void> {
|
|
84
|
-
let data = await this.fetch()
|
|
85
|
-
let entities = data.map($ => this.core.convertor($))
|
|
86
|
-
this.callback(entities)
|
|
87
|
-
ui.log(this.core.tableName, this.logType, entities)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async then<Y, N>(
|
|
91
|
-
onfulfilled?: onfulfilled<void, Y>,
|
|
92
|
-
onrejected?: onrejected<N>
|
|
93
|
-
): Promise<Y | N> {
|
|
94
|
-
return this.run().then(onfulfilled, onrejected)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
abstract class FilterBuilder<T, R> extends Builder<T, R> {
|
|
99
|
-
protected filters: Filter<R>[] = []
|
|
100
|
-
|
|
101
|
-
eq<K extends Key<R>>(key: K, val: R[K]): this {
|
|
102
|
-
this.filters.push({ type: 'eq', payload: { key, val } })
|
|
103
|
-
return this
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
in<K extends Key<R>>(key: K, vals: R[K][]): this {
|
|
107
|
-
this.filters.push({ type: 'in', payload: { key, vals } })
|
|
108
|
-
return this
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
like<K extends Key<R>>(key: K, pattern: string): this {
|
|
112
|
-
this.filters.push({ type: 'like', payload: { key, pattern } })
|
|
113
|
-
return this
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
limit(count: number): this {
|
|
117
|
-
this.filters.push({ type: 'limit', payload: count })
|
|
118
|
-
return this
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
order<K extends Key<R>>(key: K, ascending: boolean = true): this {
|
|
122
|
-
this.filters.push({ type: 'order', payload: { key, asc: ascending } })
|
|
123
|
-
return this
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export class SelectBuilder<T, R> extends FilterBuilder<T, R> {
|
|
128
|
-
protected logType = 'select'
|
|
129
|
-
|
|
130
|
-
protected override async fetch() {
|
|
131
|
-
return this.core.fetchSelect(this.filters)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export class SelectInBuilder<T, R, K extends Key<R>> extends FilterBuilder<
|
|
136
|
-
T,
|
|
137
|
-
R
|
|
138
|
-
> {
|
|
139
|
-
protected logType = 'selectIn'
|
|
140
|
-
|
|
141
|
-
constructor(
|
|
142
|
-
private key: K,
|
|
143
|
-
private vals: R[K][],
|
|
144
|
-
core: Core<T, R>,
|
|
145
|
-
callback: (objs: T[]) => void
|
|
146
|
-
) {
|
|
147
|
-
super(core, callback)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
protected override async fetch() {
|
|
151
|
-
return this.core.fetchSelectIn(this.key, this.vals, this.filters)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export class SelectRPCBuilder<T, R> extends FilterBuilder<T, R> {
|
|
156
|
-
protected logType = 'rpc'
|
|
157
|
-
|
|
158
|
-
constructor(
|
|
159
|
-
private param: object,
|
|
160
|
-
core: Core<T, R>,
|
|
161
|
-
callback: (objs: T[]) => void
|
|
162
|
-
) {
|
|
163
|
-
super(core, callback)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
protected override async fetch() {
|
|
167
|
-
return this.core.fetchRPC(this.param, this.filters)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export class InsertBuilder<T, R> extends Builder<T, R> {
|
|
172
|
-
protected logType = 'insert'
|
|
173
|
-
|
|
174
|
-
constructor(
|
|
175
|
-
private rows: Partial<R>[],
|
|
176
|
-
core: Core<T, R>,
|
|
177
|
-
callback: (objs: T[]) => void
|
|
178
|
-
) {
|
|
179
|
-
super(core, callback)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
protected override async fetch() {
|
|
183
|
-
return this.core.fetchInsert(this.rows)
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export class UpdateBuilder<T, R> extends Builder<T, R> {
|
|
188
|
-
protected logType = 'update'
|
|
189
|
-
|
|
190
|
-
constructor(
|
|
191
|
-
private idVal: R[keyof R],
|
|
192
|
-
private row: Partial<R>,
|
|
193
|
-
core: Core<T, R>,
|
|
194
|
-
callback: (objs: T[]) => void
|
|
195
|
-
) {
|
|
196
|
-
super(core, callback)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
protected override async fetch() {
|
|
200
|
-
return this.core.fetchUpdate(this.idVal, this.row)
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export class DeleteBuilder<T, R> extends Builder<T, R> {
|
|
205
|
-
protected logType = 'delete'
|
|
206
|
-
|
|
207
|
-
constructor(
|
|
208
|
-
private idVal: R[keyof R],
|
|
209
|
-
core: Core<T, R>,
|
|
210
|
-
callback: (objs: T[]) => void
|
|
211
|
-
) {
|
|
212
|
-
super(core, callback)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
protected override async fetch() {
|
|
216
|
-
return this.core.fetchDelete(this.idVal)
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export class UpsertBuilder<T, R> extends Builder<T, R> {
|
|
221
|
-
protected logType = 'upsert'
|
|
222
|
-
|
|
223
|
-
constructor(
|
|
224
|
-
private row: Partial<R>,
|
|
225
|
-
private conflictKeys: Key<R>[],
|
|
226
|
-
core: Core<T, R>,
|
|
227
|
-
callback: (objs: T[]) => void
|
|
228
|
-
) {
|
|
229
|
-
super(core, callback)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
protected override async fetch() {
|
|
233
|
-
return this.core.fetchUpsert(this.row, this.conflictKeys)
|
|
234
|
-
}
|
|
235
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { qDialog } from '../../dialog'
|
|
2
|
-
import * as qNotify from '../../notify'
|
|
3
|
-
import { LoadingBar } from 'quasar'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* I am responsible for providing UI interaction functions using Quasar.
|
|
7
|
-
* Purely functional.
|
|
8
|
-
*/
|
|
9
|
-
class UI {
|
|
10
|
-
loadingOff(): void {
|
|
11
|
-
LoadingBar.setDefaults({ size: '0px' })
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
loadingOn(): void {
|
|
15
|
-
setTimeout(() => LoadingBar.setDefaults({ size: '5px' }), 3000)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
onConfirm(title: string, msg: string): Promise<void> {
|
|
19
|
-
return qDialog.confirm(title, msg)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
onNotify(msg: string): void {
|
|
23
|
-
qNotify.toast(msg)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
log(table: string, type: string, data: any[]): void {
|
|
27
|
-
console.dev(`[${table}] ${type}`, data)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const ui = new UI()
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { supabase, handleThrow, PostgrestFilterBuilder } from '../../supabase'
|
|
2
|
-
import { Filter } from '../type'
|
|
3
|
-
|
|
4
|
-
type FilterBuilder = PostgrestFilterBuilder<any, any, any, any>
|
|
5
|
-
|
|
6
|
-
async function send(q: any): Promise<any[]> {
|
|
7
|
-
let data = await q.then(handleThrow)
|
|
8
|
-
return Array.isArray(data) ? data : [data]
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function parseFilters(q: FilterBuilder, filters: Filter[]): FilterBuilder {
|
|
12
|
-
let sortedFilters = filters.sortedBy(f =>
|
|
13
|
-
['eq', 'in', 'like', 'limit', 'order'].indexOf(f.type)
|
|
14
|
-
)
|
|
15
|
-
for (const { type: t, payload: p } of sortedFilters) {
|
|
16
|
-
if (t === 'eq') q = q.eq(p.key, p.val)
|
|
17
|
-
if (t === 'in') q = q.in(p.key, p.vals)
|
|
18
|
-
if (t === 'like') q = q.like(p.key, p.pattern)
|
|
19
|
-
if (t === 'limit') q = q.limit(p)
|
|
20
|
-
if (t === 'order') q = q.order(p.key, { ascending: p.asc })
|
|
21
|
-
}
|
|
22
|
-
return q
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* I am responsible for providing database operation.
|
|
27
|
-
* Purely functional.
|
|
28
|
-
*/
|
|
29
|
-
class DB {
|
|
30
|
-
async select(
|
|
31
|
-
table: string,
|
|
32
|
-
fields: string[],
|
|
33
|
-
filters: Filter[]
|
|
34
|
-
): Promise<any[]> {
|
|
35
|
-
let q = supabase.from(table).select(fields.join(','))
|
|
36
|
-
q = parseFilters(q, filters)
|
|
37
|
-
return send(q)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async selectIn(
|
|
41
|
-
table: string,
|
|
42
|
-
fields: string[],
|
|
43
|
-
key: string,
|
|
44
|
-
vals: any[],
|
|
45
|
-
filters: Filter[]
|
|
46
|
-
): Promise<any[]> {
|
|
47
|
-
let getRows = (chunkPiece: any[]): Promise<any[]> => {
|
|
48
|
-
let q = supabase
|
|
49
|
-
.from(table)
|
|
50
|
-
.select(fields.join(','))
|
|
51
|
-
.in(key, chunkPiece)
|
|
52
|
-
q = parseFilters(q, filters)
|
|
53
|
-
return send(q)
|
|
54
|
-
}
|
|
55
|
-
let promises = vals.chunk(100).map(getRows)
|
|
56
|
-
let result = await Promise.all(promises)
|
|
57
|
-
return result.flat()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async rpc(fn: string, param: any, filters: Filter[]): Promise<any[]> {
|
|
61
|
-
let q = supabase.rpc(fn, param)
|
|
62
|
-
q = parseFilters(q, filters)
|
|
63
|
-
return send(q)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async insert(table: string, rows: any[]): Promise<any[]> {
|
|
67
|
-
let q = supabase.from(table).insert(rows).select('*')
|
|
68
|
-
return send(q)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async update(
|
|
72
|
-
table: string,
|
|
73
|
-
idKey: string,
|
|
74
|
-
idVal: any,
|
|
75
|
-
row: any
|
|
76
|
-
): Promise<any[]> {
|
|
77
|
-
let q = supabase
|
|
78
|
-
.from(table)
|
|
79
|
-
.update(row)
|
|
80
|
-
.eq(idKey, idVal)
|
|
81
|
-
.select('*')
|
|
82
|
-
.single()
|
|
83
|
-
return send(q)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async delete(table: string, idKey: string, idVal: any): Promise<any[]> {
|
|
87
|
-
let q = supabase
|
|
88
|
-
.from(table)
|
|
89
|
-
.delete()
|
|
90
|
-
.eq(idKey, idVal)
|
|
91
|
-
.select('*')
|
|
92
|
-
.single()
|
|
93
|
-
return send(q)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async upsert(
|
|
97
|
-
table: string,
|
|
98
|
-
idKey: string,
|
|
99
|
-
row: any,
|
|
100
|
-
conflictKeys: (string & keyof typeof row)[]
|
|
101
|
-
): Promise<any[]> {
|
|
102
|
-
let conflict: any = {}
|
|
103
|
-
for (let f of conflictKeys) {
|
|
104
|
-
conflict[f] = row[f]
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
let q = supabase.from(table).select('*').match(conflict)
|
|
108
|
-
let data = await send(q)
|
|
109
|
-
|
|
110
|
-
if (data.length > 1)
|
|
111
|
-
throw 'Fail to upsert! More than 1 rows matching the conflictKeys are found!'
|
|
112
|
-
|
|
113
|
-
if (data.length === 1) {
|
|
114
|
-
let id = data[0][idKey as keyof (typeof data)[0]]
|
|
115
|
-
return await this.update(table, idKey, id, row)
|
|
116
|
-
} else {
|
|
117
|
-
return await this.insert(table, [row])
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export const db = new DB()
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { Filter, Key } from '../type'
|
|
2
|
-
import { db } from './db'
|
|
3
|
-
|
|
4
|
-
/** I am responsible for transfering the state from Table to Builders.*/
|
|
5
|
-
export class Core<T, R> {
|
|
6
|
-
constructor(
|
|
7
|
-
public tableName: string,
|
|
8
|
-
public idKey: Key<R>,
|
|
9
|
-
public fields: Key<R>[],
|
|
10
|
-
public convertor: ($: R) => T
|
|
11
|
-
) {}
|
|
12
|
-
|
|
13
|
-
async fetchSelect(filters: Filter<R>[]): Promise<R[]> {
|
|
14
|
-
return db.select(this.tableName, this.fields, filters)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async fetchSelectIn<K extends Key<R>>(
|
|
18
|
-
key: K,
|
|
19
|
-
vals: R[K][],
|
|
20
|
-
filters: Filter<R>[]
|
|
21
|
-
): Promise<R[]> {
|
|
22
|
-
return db.selectIn(this.tableName, this.fields, key, vals, filters)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async fetchRPC(param: object, filters: Filter<R>[]): Promise<R[]> {
|
|
26
|
-
return db.rpc(this.tableName, param, filters)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async fetchInsert(rows: Partial<R>[]): Promise<R[]> {
|
|
30
|
-
return db.insert(this.tableName, rows)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async fetchUpdate(idVal: R[keyof R], row: Partial<R>): Promise<R[]> {
|
|
34
|
-
return db.update(this.tableName, this.idKey, idVal, row)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async fetchDelete(idVal: R[keyof R]): Promise<R[]> {
|
|
38
|
-
return db.delete(this.tableName, this.idKey, idVal)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async fetchUpsert(row: Partial<R>, conflictKeys: Key<R>[]): Promise<R[]> {
|
|
42
|
-
return db.upsert(this.tableName, this.idKey, row, conflictKeys)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export type Filter<R = any> =
|
|
2
|
-
| { type: 'eq'; payload: { key: Key<R>; val: any } }
|
|
3
|
-
| { type: 'in'; payload: { key: Key<R>; vals: any[] } }
|
|
4
|
-
| { type: 'like'; payload: { key: Key<R>; pattern: string } }
|
|
5
|
-
| { type: 'limit'; payload: number }
|
|
6
|
-
| { type: 'order'; payload: { key: Key<R>; asc: boolean } }
|
|
7
|
-
|
|
8
|
-
export type Constructor<T extends object = {}> = new (...args: any[]) => T
|
|
9
|
-
|
|
10
|
-
export type BooleanKeys<T> = {
|
|
11
|
-
[k in keyof T]: T[k] extends boolean ? k : never
|
|
12
|
-
}[keyof T]
|
|
13
|
-
// type OnlyBoolean<T> = { [k in BooleanKeys<T>]: boolean }
|
|
14
|
-
|
|
15
|
-
export type Key<R> = string & keyof R
|