@d-mok/quasar-app-extension-quasar-axe 2.2.3 → 2.2.4
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 +1 -1
- package/src/templates/src/utils/index.ts +2 -1
- package/src/templates/src/utils/puppets/index.ts +0 -1
- package/src/templates/src/utils/{puppets → puppets2}/ORM.ts +3 -3
- package/src/templates/src/utils/puppets2/builder/index.ts +226 -0
- package/src/templates/src/utils/puppets2/builder/ui.ts +31 -0
- package/src/templates/src/utils/puppets2/core/db.ts +127 -0
- package/src/templates/src/utils/puppets2/core/index.ts +44 -0
- package/src/templates/src/utils/puppets2/index.ts +1 -0
- package/src/templates/src/utils/puppets2/type.ts +15 -0
- /package/src/templates/src/utils/{puppets → puppets2}/test.ts +0 -0
package/package.json
CHANGED
|
@@ -6,7 +6,8 @@ export * as qNotify from './notify'
|
|
|
6
6
|
|
|
7
7
|
export { HANDLE_ERROR, supabase } from './supabase'
|
|
8
8
|
|
|
9
|
-
export { TABLE, ENTITY
|
|
9
|
+
export { TABLE, ENTITY } from './puppets'
|
|
10
|
+
export { ORM } from './puppets2'
|
|
10
11
|
|
|
11
12
|
import { supabase } from './supabase'
|
|
12
13
|
|
|
@@ -18,7 +18,7 @@ export function ORM<
|
|
|
18
18
|
type R = InstanceType<RBase>
|
|
19
19
|
|
|
20
20
|
class E extends RClass {
|
|
21
|
-
private _hostTable:
|
|
21
|
+
private _hostTable: InstanceType<ReturnType<typeof getTable>>
|
|
22
22
|
|
|
23
23
|
constructor(...args: any[]) {
|
|
24
24
|
super()
|
|
@@ -69,8 +69,8 @@ export function ORM<
|
|
|
69
69
|
return rec
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
protected onChange():
|
|
73
|
-
protected onEdit():
|
|
72
|
+
protected onChange(): void {}
|
|
73
|
+
protected onEdit(): void {}
|
|
74
74
|
protected onSanitize(row: Partial<R>): Partial<R> {
|
|
75
75
|
return row
|
|
76
76
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
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 isSlience: 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.isSlience) 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.isSlience) 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
|
+
slience(): this {
|
|
56
|
+
this.isSlience = true
|
|
57
|
+
this.isNotify = false
|
|
58
|
+
return this
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
abstract class Builder<T, R> extends BuilderUI implements PromiseLike<void> {
|
|
63
|
+
constructor(
|
|
64
|
+
protected core: Core<T, R>,
|
|
65
|
+
private callback: (objs: T[]) => void
|
|
66
|
+
) {
|
|
67
|
+
super()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected abstract logType: string
|
|
71
|
+
|
|
72
|
+
protected abstract fetch(): Promise<R[]>
|
|
73
|
+
|
|
74
|
+
protected override async dataLogic(): Promise<void> {
|
|
75
|
+
let data = await this.fetch()
|
|
76
|
+
let entities = data.map($ => this.core.convertor($))
|
|
77
|
+
this.callback(entities)
|
|
78
|
+
ui.log(this.core.tableName, this.logType, entities)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async then<Y, N>(
|
|
82
|
+
onfulfilled?: onfulfilled<void, Y>,
|
|
83
|
+
onrejected?: onrejected<N>
|
|
84
|
+
): Promise<Y | N> {
|
|
85
|
+
return this.run().then(onfulfilled, onrejected)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
abstract class FilterBuilder<T, R> extends Builder<T, R> {
|
|
90
|
+
protected filters: Filter<R>[] = []
|
|
91
|
+
|
|
92
|
+
eq<K extends Key<R>>(key: K, val: R[K]): this {
|
|
93
|
+
this.filters.push({ type: 'eq', payload: { key, val } })
|
|
94
|
+
return this
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
in<K extends Key<R>>(key: K, vals: R[K][]): this {
|
|
98
|
+
this.filters.push({ type: 'in', payload: { key, vals } })
|
|
99
|
+
return this
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
like<K extends Key<R>>(key: K, pattern: string): this {
|
|
103
|
+
this.filters.push({ type: 'like', payload: { key, pattern } })
|
|
104
|
+
return this
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
limit(count: number): this {
|
|
108
|
+
this.filters.push({ type: 'limit', payload: count })
|
|
109
|
+
return this
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
order<K extends Key<R>>(key: K, ascending: boolean = true): this {
|
|
113
|
+
this.filters.push({ type: 'order', payload: { key, asc: ascending } })
|
|
114
|
+
return this
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export class SelectBuilder<T, R> extends FilterBuilder<T, R> {
|
|
119
|
+
protected logType = 'select'
|
|
120
|
+
|
|
121
|
+
protected override async fetch() {
|
|
122
|
+
return this.core.fetchSelect(this.filters)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class SelectInBuilder<T, R, K extends Key<R>> extends FilterBuilder<
|
|
127
|
+
T,
|
|
128
|
+
R
|
|
129
|
+
> {
|
|
130
|
+
protected logType = 'selectIn'
|
|
131
|
+
|
|
132
|
+
constructor(
|
|
133
|
+
private key: K,
|
|
134
|
+
private vals: R[K][],
|
|
135
|
+
core: Core<T, R>,
|
|
136
|
+
callback: (objs: T[]) => void
|
|
137
|
+
) {
|
|
138
|
+
super(core, callback)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
protected override async fetch() {
|
|
142
|
+
return this.core.fetchSelectIn(this.key, this.vals, this.filters)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export class SelectRPCBuilder<T, R> extends FilterBuilder<T, R> {
|
|
147
|
+
protected logType = 'rpc'
|
|
148
|
+
|
|
149
|
+
constructor(
|
|
150
|
+
private param: object,
|
|
151
|
+
core: Core<T, R>,
|
|
152
|
+
callback: (objs: T[]) => void
|
|
153
|
+
) {
|
|
154
|
+
super(core, callback)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
protected override async fetch() {
|
|
158
|
+
return this.core.fetchRPC(this.param, this.filters)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export class InsertBuilder<T, R> extends Builder<T, R> {
|
|
163
|
+
protected logType = 'insert'
|
|
164
|
+
|
|
165
|
+
constructor(
|
|
166
|
+
private rows: Partial<R>[],
|
|
167
|
+
core: Core<T, R>,
|
|
168
|
+
callback: (objs: T[]) => void
|
|
169
|
+
) {
|
|
170
|
+
super(core, callback)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected override async fetch() {
|
|
174
|
+
return this.core.fetchInsert(this.rows)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export class UpdateBuilder<T, R> extends Builder<T, R> {
|
|
179
|
+
protected logType = 'update'
|
|
180
|
+
|
|
181
|
+
constructor(
|
|
182
|
+
private idVal: R[keyof R],
|
|
183
|
+
private row: Partial<R>,
|
|
184
|
+
core: Core<T, R>,
|
|
185
|
+
callback: (objs: T[]) => void
|
|
186
|
+
) {
|
|
187
|
+
super(core, callback)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
protected override async fetch() {
|
|
191
|
+
return this.core.fetchUpdate(this.idVal, this.row)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export class DeleteBuilder<T, R> extends Builder<T, R> {
|
|
196
|
+
protected logType = 'delete'
|
|
197
|
+
|
|
198
|
+
constructor(
|
|
199
|
+
private idVal: R[keyof R],
|
|
200
|
+
core: Core<T, R>,
|
|
201
|
+
callback: (objs: T[]) => void
|
|
202
|
+
) {
|
|
203
|
+
super(core, callback)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
protected override async fetch() {
|
|
207
|
+
return this.core.fetchDelete(this.idVal)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export class UpsertBuilder<T, R> extends Builder<T, R> {
|
|
212
|
+
protected logType = 'upsert'
|
|
213
|
+
|
|
214
|
+
constructor(
|
|
215
|
+
private row: Partial<R>,
|
|
216
|
+
private conflictKeys: Key<R>[],
|
|
217
|
+
core: Core<T, R>,
|
|
218
|
+
callback: (objs: T[]) => void
|
|
219
|
+
) {
|
|
220
|
+
super(core, callback)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
protected override async fetch() {
|
|
224
|
+
return this.core.fetchUpsert(this.row, this.conflictKeys)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { qDialog } from '../..'
|
|
2
|
+
import { qNotify } from '../..'
|
|
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()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
supabase,
|
|
3
|
+
HANDLE_ERROR,
|
|
4
|
+
PostgrestFilterBuilder,
|
|
5
|
+
} from '../../supabase'
|
|
6
|
+
import { Filter } from '../type'
|
|
7
|
+
|
|
8
|
+
type FilterBuilder = PostgrestFilterBuilder<any, any, any, any>
|
|
9
|
+
|
|
10
|
+
async function send(q: any): Promise<any[]> {
|
|
11
|
+
let { data, error } = await q
|
|
12
|
+
HANDLE_ERROR(data, error)
|
|
13
|
+
return Array.isArray(data) ? data : [data]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseFilters(q: FilterBuilder, filters: Filter[]): FilterBuilder {
|
|
17
|
+
let sortedFilters = filters.sortedBy(f =>
|
|
18
|
+
['eq', 'in', 'like', 'limit', 'order'].indexOf(f.type)
|
|
19
|
+
)
|
|
20
|
+
for (const { type: t, payload: p } of sortedFilters) {
|
|
21
|
+
if (t === 'eq') q = q.eq(p.key, p.val)
|
|
22
|
+
if (t === 'in') q = q.in(p.key, p.vals)
|
|
23
|
+
if (t === 'like') q = q.like(p.key, p.pattern)
|
|
24
|
+
if (t === 'limit') q = q.limit(p)
|
|
25
|
+
if (t === 'order') q = q.order(p.key, { ascending: p.asc })
|
|
26
|
+
}
|
|
27
|
+
return q
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* I am responsible for providing database operation.
|
|
32
|
+
* Purely functional.
|
|
33
|
+
*/
|
|
34
|
+
class DB {
|
|
35
|
+
async select(
|
|
36
|
+
table: string,
|
|
37
|
+
fields: string[],
|
|
38
|
+
filters: Filter[]
|
|
39
|
+
): Promise<any[]> {
|
|
40
|
+
let q = supabase.from(table).select(fields.join(','))
|
|
41
|
+
q = parseFilters(q, filters)
|
|
42
|
+
return send(q)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async selectIn(
|
|
46
|
+
table: string,
|
|
47
|
+
fields: string[],
|
|
48
|
+
key: string,
|
|
49
|
+
vals: any[],
|
|
50
|
+
filters: Filter[]
|
|
51
|
+
): Promise<any[]> {
|
|
52
|
+
let getRows = (chunkPiece: any[]): Promise<any[]> => {
|
|
53
|
+
let q = supabase
|
|
54
|
+
.from(table)
|
|
55
|
+
.select(fields.join(','))
|
|
56
|
+
.in(key, chunkPiece)
|
|
57
|
+
q = parseFilters(q, filters)
|
|
58
|
+
return send(q)
|
|
59
|
+
}
|
|
60
|
+
let promises = vals.chunk(100).map(getRows)
|
|
61
|
+
let result = await Promise.all(promises)
|
|
62
|
+
return result.flat()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async rpc(fn: string, param: any, filters: Filter[]): Promise<any[]> {
|
|
66
|
+
let q = supabase.rpc(fn, param)
|
|
67
|
+
q = parseFilters(q, filters)
|
|
68
|
+
return send(q)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async insert(table: string, rows: any[]): Promise<any[]> {
|
|
72
|
+
let q = supabase.from(table).insert(rows).select('*')
|
|
73
|
+
return send(q)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async update(
|
|
77
|
+
table: string,
|
|
78
|
+
idKey: string,
|
|
79
|
+
idVal: any,
|
|
80
|
+
row: any
|
|
81
|
+
): Promise<any[]> {
|
|
82
|
+
let q = supabase
|
|
83
|
+
.from(table)
|
|
84
|
+
.update(row)
|
|
85
|
+
.eq(idKey, idVal)
|
|
86
|
+
.select('*')
|
|
87
|
+
.single()
|
|
88
|
+
return send(q)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async delete(table: string, idKey: string, idVal: any): Promise<any[]> {
|
|
92
|
+
let q = supabase
|
|
93
|
+
.from(table)
|
|
94
|
+
.delete()
|
|
95
|
+
.eq(idKey, idVal)
|
|
96
|
+
.select('*')
|
|
97
|
+
.single()
|
|
98
|
+
return send(q)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async upsert(
|
|
102
|
+
table: string,
|
|
103
|
+
idKey: string,
|
|
104
|
+
row: any,
|
|
105
|
+
conflictKeys: (string & keyof typeof row)[]
|
|
106
|
+
): Promise<any[]> {
|
|
107
|
+
let conflict: any = {}
|
|
108
|
+
for (let f of conflictKeys) {
|
|
109
|
+
conflict[f] = row[f]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let q = supabase.from(table).select('*').match(conflict)
|
|
113
|
+
let data = await send(q)
|
|
114
|
+
|
|
115
|
+
if (data.length > 1)
|
|
116
|
+
throw 'Fail to upsert! More than 1 rows matching the conflictKeys are found!'
|
|
117
|
+
|
|
118
|
+
if (data.length === 1) {
|
|
119
|
+
let id = data[0][idKey as keyof (typeof data)[0]]
|
|
120
|
+
return await this.update(table, idKey, id, row)
|
|
121
|
+
} else {
|
|
122
|
+
return await this.insert(table, [row])
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const db = new DB()
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ORM } from './ORM'
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
|
File without changes
|