@d-mok/quasar-app-extension-quasar-axe 1.0.2 → 1.0.6
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/boot/RegComp.ts +1 -0
- package/src/utils/ORM/basic.ts +82 -93
- package/src/utils/ORM/table.ts +62 -4
- package/src/utils/ORM/view.ts +23 -44
- package/src/utils/supabase.ts +1 -1
package/package.json
CHANGED
package/src/boot/RegComp.ts
CHANGED
package/src/utils/ORM/basic.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { qDialog } from '../dialog'
|
|
|
3
3
|
import { qNotify } from '../notify'
|
|
4
4
|
import { LoadingBar } from 'quasar'
|
|
5
5
|
import { Ordering, Criteria, strKeyOf, Sheet } from 'sapphire-js'
|
|
6
|
+
import { reactive } from 'vue'
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
export type Where<T> =
|
|
@@ -11,29 +12,20 @@ export type Where<T> =
|
|
|
11
12
|
| T[K][]
|
|
12
13
|
| { like: string }
|
|
13
14
|
}
|
|
14
|
-
| ((_: PostgrestFilterBuilder) => any)
|
|
15
15
|
|
|
16
|
-
// export type OrderBy<T> =
|
|
17
|
-
// strKeyOf<T>
|
|
18
|
-
// | { [K in keyof T]?: true | false }
|
|
19
16
|
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
type Action = 'select' | 'insert' | 'update' | 'delete' | 'patch'
|
|
23
|
-
|
|
18
|
+
type Action = 'select' | 'insert' | 'update' | 'delete'
|
|
24
19
|
|
|
25
20
|
|
|
26
21
|
function loadingBar(on: boolean): void {
|
|
27
|
-
on
|
|
22
|
+
on
|
|
23
|
+
? setTimeout(() => LoadingBar.setDefaults({ size: "5px" }), 3000)
|
|
28
24
|
: LoadingBar.setDefaults({ size: "0px" })
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
29
|
/**
|
|
38
30
|
* A subclass of array designed as an ORM.
|
|
39
31
|
* @template T - type of element class
|
|
@@ -41,28 +33,38 @@ function loadingBar(on: boolean): void {
|
|
|
41
33
|
*/
|
|
42
34
|
export abstract class BasicTable<T extends R, R extends object> extends Sheet<T>{
|
|
43
35
|
|
|
36
|
+
constructor(...args: any) {
|
|
37
|
+
super(args)
|
|
38
|
+
if (this.caching) {
|
|
39
|
+
this.restoreCache()
|
|
40
|
+
setInterval(() => this.screenCache(), 6000 + Math.random() * 3000)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
44
|
/**
|
|
45
45
|
* The name of the database table.
|
|
46
46
|
*/
|
|
47
47
|
public abstract readonly tableName: string
|
|
48
|
+
|
|
48
49
|
/**
|
|
49
50
|
* The name of the ID field.
|
|
50
51
|
*/
|
|
51
52
|
public abstract readonly idField: strKeyOf<R>
|
|
53
|
+
|
|
52
54
|
/**
|
|
53
55
|
* The class of the elements.
|
|
54
56
|
*/
|
|
55
57
|
public abstract readonly entityClass: new (_: Partial<R>) => T
|
|
58
|
+
|
|
56
59
|
/**
|
|
57
60
|
* The default ordering instruction object.
|
|
58
61
|
*/
|
|
59
62
|
public readonly ordering: Readonly<Ordering<T>> = []
|
|
60
|
-
// /**
|
|
61
|
-
// * The default fields to select
|
|
62
|
-
// */
|
|
63
|
-
// public readonly selectFields: strKeyOf<R>[] = []
|
|
64
|
-
|
|
65
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Auto Cache
|
|
66
|
+
*/
|
|
67
|
+
public readonly caching: boolean = false
|
|
66
68
|
|
|
67
69
|
private convert(data: R[]): T[] {
|
|
68
70
|
return data.map($ => new this.entityClass($))
|
|
@@ -74,41 +76,21 @@ export abstract class BasicTable<T extends R, R extends object> extends Sheet<T>
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
// private defaultOrder(): void {
|
|
79
|
-
// if (this.ordering !== undefined)
|
|
80
|
-
// this.order(...this.ordering)
|
|
81
|
-
// }
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// private selectedFields() {
|
|
85
|
-
// return this.selectFields === undefined ? "*" : this.selectFields.join(",")
|
|
86
|
-
// }
|
|
87
|
-
|
|
88
|
-
|
|
89
79
|
protected idWhere(id: R[this['idField']]): any {
|
|
90
80
|
return { [this.idField]: id }
|
|
91
81
|
}
|
|
92
82
|
|
|
93
83
|
|
|
94
|
-
protected idsWhere(ids: R[this['idField']][]): any {
|
|
95
|
-
return { [this.idField]: ids }
|
|
96
|
-
}
|
|
97
84
|
|
|
98
85
|
|
|
99
|
-
protected orderById(ids: R[this['idField']][]): void {
|
|
100
|
-
let ord = { [this.idField]: ids } as any
|
|
101
|
-
this.order(ord)
|
|
102
|
-
}
|
|
103
86
|
|
|
104
87
|
|
|
105
|
-
|
|
88
|
+
|
|
89
|
+
private APIAction(type: Action, content: Partial<R>[]) {
|
|
106
90
|
let q = supabase.from<R>(this.tableName)
|
|
107
91
|
switch (type) {
|
|
108
92
|
case 'select':
|
|
109
93
|
return q.select('*')
|
|
110
|
-
case 'patch':
|
|
111
|
-
return q.select('*')
|
|
112
94
|
case 'delete':
|
|
113
95
|
return q.delete()
|
|
114
96
|
case 'insert':
|
|
@@ -120,21 +102,19 @@ export abstract class BasicTable<T extends R, R extends object> extends Sheet<T>
|
|
|
120
102
|
|
|
121
103
|
protected async API({
|
|
122
104
|
type,
|
|
123
|
-
content,
|
|
124
|
-
where,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
slience
|
|
105
|
+
content = [],
|
|
106
|
+
where = {},
|
|
107
|
+
custom = $ => $,
|
|
108
|
+
action = ($: any) => { },
|
|
109
|
+
confirm = undefined,
|
|
110
|
+
notify = undefined,
|
|
111
|
+
slience = false
|
|
131
112
|
}: {
|
|
132
113
|
type: Action
|
|
133
114
|
content?: Partial<R>[]
|
|
134
115
|
where?: Where<R>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
action: (entities: T[]) => void
|
|
116
|
+
custom?: (_: PostgrestFilterBuilder) => PostgrestFilterBuilder
|
|
117
|
+
action?: (entities: T[]) => void
|
|
138
118
|
confirm?: [string, string?]
|
|
139
119
|
notify?: string
|
|
140
120
|
slience?: boolean
|
|
@@ -144,12 +124,12 @@ export abstract class BasicTable<T extends R, R extends object> extends Sheet<T>
|
|
|
144
124
|
|
|
145
125
|
let q = this.APIAction(type, content)
|
|
146
126
|
q = applyWhere(q, where)
|
|
147
|
-
|
|
148
|
-
// q = applyLimit(q, limit)
|
|
127
|
+
q = custom(q)
|
|
149
128
|
|
|
150
129
|
let { data, error } = await q
|
|
151
130
|
handleError(data, error)
|
|
152
131
|
|
|
132
|
+
if (this.caching) this.pushCache(data)
|
|
153
133
|
let entities = this.convert(data)
|
|
154
134
|
action(entities)
|
|
155
135
|
this.order(...this.ordering)
|
|
@@ -161,68 +141,77 @@ export abstract class BasicTable<T extends R, R extends object> extends Sheet<T>
|
|
|
161
141
|
|
|
162
142
|
|
|
163
143
|
|
|
164
|
-
|
|
165
|
-
|
|
166
144
|
/**
|
|
167
145
|
* Get an item by `criteria`. If not found, return a new object.
|
|
168
146
|
*/
|
|
169
147
|
got(criteria: Criteria<T>): T {
|
|
170
148
|
return this.get(criteria) ?? new this.entityClass({})
|
|
171
149
|
}
|
|
172
|
-
}
|
|
173
150
|
|
|
174
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Get a reactive copy of this.
|
|
154
|
+
*/
|
|
155
|
+
reactive(): this {
|
|
156
|
+
return reactive(this) as this
|
|
157
|
+
}
|
|
175
158
|
|
|
176
159
|
|
|
177
160
|
|
|
161
|
+
private get cache(): R[] {
|
|
162
|
+
return JSON.parse(localStorage.getItem(this.tableName) ?? '[]')
|
|
163
|
+
}
|
|
178
164
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
165
|
+
private set cache(data: R[]) {
|
|
166
|
+
let json = JSON.stringify(data)
|
|
167
|
+
localStorage.setItem(this.tableName, json)
|
|
168
|
+
}
|
|
182
169
|
|
|
183
|
-
|
|
184
|
-
|
|
170
|
+
private restoreCache() {
|
|
171
|
+
let entities = this.convert(this.cache)
|
|
172
|
+
this.set(entities)
|
|
173
|
+
}
|
|
185
174
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (Array.isArray(val)) {
|
|
189
|
-
// treat as in
|
|
190
|
-
q = q.in(key, val)
|
|
191
|
-
} else if (typeof val === 'object') {
|
|
192
|
-
if ('like' in val) {
|
|
193
|
-
// treat as like
|
|
194
|
-
let { like } = val as { like: string }
|
|
195
|
-
q = q.like(key, like)
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
// treat as eq
|
|
199
|
-
q = q.eq(key, val)
|
|
200
|
-
}
|
|
175
|
+
private pushCache(data: R[]) {
|
|
176
|
+
this.cache = [...this.cache, ...data]
|
|
201
177
|
}
|
|
202
|
-
|
|
178
|
+
|
|
179
|
+
private screenCache() {
|
|
180
|
+
let ids: any[] = this.scan(this.idField)
|
|
181
|
+
this.cache = this.cache.filter($ => ids.includes($[this.idField]))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
203
188
|
}
|
|
204
189
|
|
|
205
|
-
// function applyOrderBy(q: PostgrestFilterBuilder, orderBy?: OrderBy<any>): PostgrestFilterBuilder {
|
|
206
|
-
// if (orderBy === undefined)
|
|
207
|
-
// return q
|
|
208
190
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
// let field = Object.keys(orderBy)[0]
|
|
213
|
-
// let ascending = orderBy[field]
|
|
214
|
-
// q = q.order(field, { ascending })
|
|
215
|
-
// }
|
|
216
|
-
// return q
|
|
217
|
-
// }
|
|
191
|
+
function whereType(val: any): string {
|
|
192
|
+
if (Array.isArray(val))
|
|
193
|
+
return 'in'
|
|
218
194
|
|
|
195
|
+
if (typeof val === 'object' && 'like' in val)
|
|
196
|
+
return 'like'
|
|
219
197
|
|
|
198
|
+
return 'eq'
|
|
199
|
+
}
|
|
220
200
|
|
|
221
|
-
// function applyLimit(q: PostgrestFilterBuilder, limit?: number): PostgrestFilterBuilder {
|
|
222
|
-
// if (limit === undefined)
|
|
223
|
-
// return q
|
|
224
201
|
|
|
225
|
-
// return q.limit(limit)
|
|
226
|
-
// }
|
|
227
202
|
|
|
203
|
+
function applyWhere(q: PostgrestFilterBuilder, where: Where<any>): PostgrestFilterBuilder {
|
|
204
|
+
for (let key in where) {
|
|
205
|
+
let val = where[key]
|
|
206
|
+
let type = whereType(val)
|
|
207
|
+
|
|
208
|
+
if (type === 'in')
|
|
209
|
+
q = q.in(key, val)
|
|
210
|
+
if (type === 'like')
|
|
211
|
+
q = q.like(key, val.like)
|
|
212
|
+
if (type === 'eq')
|
|
213
|
+
q = q.eq(key, val)
|
|
214
|
+
}
|
|
215
|
+
return q
|
|
216
|
+
}
|
|
228
217
|
|
package/src/utils/ORM/table.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { TableView } from './view'
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
type muteOptions = {
|
|
7
|
-
confirm?:
|
|
7
|
+
confirm?: [string, string?]
|
|
8
8
|
notify?: string
|
|
9
9
|
slience?: boolean
|
|
10
10
|
}
|
|
@@ -28,7 +28,7 @@ export abstract class Table<T extends R, R extends object> extends TableView<T,
|
|
|
28
28
|
await this.API({
|
|
29
29
|
type: 'insert',
|
|
30
30
|
content: Array.isArray(rows) ? rows : [rows],
|
|
31
|
-
action:
|
|
31
|
+
action: $ => this.push(...$),
|
|
32
32
|
confirm,
|
|
33
33
|
notify,
|
|
34
34
|
slience
|
|
@@ -45,7 +45,7 @@ export abstract class Table<T extends R, R extends object> extends TableView<T,
|
|
|
45
45
|
type: 'update',
|
|
46
46
|
content: [row],
|
|
47
47
|
where: this.idWhere(id),
|
|
48
|
-
action:
|
|
48
|
+
action: $ => this.merge($, this.idField),
|
|
49
49
|
confirm,
|
|
50
50
|
notify,
|
|
51
51
|
slience
|
|
@@ -88,7 +88,7 @@ export abstract class Table<T extends R, R extends object> extends TableView<T,
|
|
|
88
88
|
await this.API({
|
|
89
89
|
type: 'delete',
|
|
90
90
|
where: this.idWhere(id),
|
|
91
|
-
action:
|
|
91
|
+
action: $ => this.discard(this.idWhere(id) as Criteria<T>),
|
|
92
92
|
confirm,
|
|
93
93
|
notify,
|
|
94
94
|
slience
|
|
@@ -153,3 +153,61 @@ export function Empower<RCls extends Constructor, R = InstanceType<RCls>>(BaseCl
|
|
|
153
153
|
|
|
154
154
|
|
|
155
155
|
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
// class FolderRow {
|
|
167
|
+
// id: number = 0
|
|
168
|
+
// subject: string = ""
|
|
169
|
+
// category: string = ""
|
|
170
|
+
// teacher: string = ""
|
|
171
|
+
// bank: string = ""
|
|
172
|
+
// name: string = ""
|
|
173
|
+
// pub_q: boolean = false
|
|
174
|
+
// pub_s: boolean = false
|
|
175
|
+
// codes: string[] = []
|
|
176
|
+
// modified: number = 0
|
|
177
|
+
|
|
178
|
+
// constructor(row = {}) {
|
|
179
|
+
// Object.assign(this, row)
|
|
180
|
+
// }
|
|
181
|
+
// }
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
// export class Folder extends FolderRow {
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
// }
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
// export class FolderTable extends Table<Folder, FolderRow>{
|
|
196
|
+
// readonly tableName = 'folder'
|
|
197
|
+
// readonly idField = 'id' as const
|
|
198
|
+
// readonly entityClass = Folder
|
|
199
|
+
// readonly ordering = [
|
|
200
|
+
// 'subject',
|
|
201
|
+
// { category: ['topic', 'paper', 'exercise'] },
|
|
202
|
+
// 'bank',
|
|
203
|
+
// 'name'
|
|
204
|
+
// ] as const
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
// }
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
// export const Folders = (new FolderTable).reactive()
|
|
212
|
+
|
|
213
|
+
// Folders.selectIn('teacher', [1,2,3])
|
package/src/utils/ORM/view.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { BasicTable, Where } from './basic'
|
|
2
|
+
import { PostgrestFilterBuilder } from '../supabase'
|
|
3
|
+
import { strKeyOf } from 'sapphire-js'
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
type selectOptions<R> = {
|
|
5
7
|
where?: Where<R>
|
|
6
|
-
|
|
7
|
-
// limit?: number
|
|
8
|
+
custom?: (_: PostgrestFilterBuilder) => PostgrestFilterBuilder
|
|
8
9
|
confirm?: [string, string?]
|
|
9
10
|
notify?: string
|
|
10
11
|
slience?: boolean
|
|
12
|
+
patch?: boolean
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
|
|
@@ -30,65 +32,42 @@ function chunk<T>(arr: T[], size: number): T[][] {
|
|
|
30
32
|
*/
|
|
31
33
|
export abstract class TableView<T extends R, R extends object> extends BasicTable<T, R>{
|
|
32
34
|
|
|
33
|
-
async select({ where, confirm, notify, slience }: selectOptions<R> = {}) {
|
|
35
|
+
async select({ where, custom, confirm, notify, slience, patch }: selectOptions<R> = {}) {
|
|
36
|
+
if (!patch) this.clear()
|
|
34
37
|
await this.API({
|
|
35
38
|
type: 'select',
|
|
36
39
|
where,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
action: entities => this.set(entities),
|
|
40
|
-
// order: true,
|
|
40
|
+
custom,
|
|
41
|
+
action: $ => patch ? this.absorb($, this.idField) : this.set($),
|
|
41
42
|
confirm,
|
|
42
43
|
notify,
|
|
43
44
|
slience
|
|
44
45
|
})
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
async patch({ where, confirm, notify, slience }: selectOptions<R> = {}) {
|
|
48
|
-
await this.API({
|
|
49
|
-
type: 'patch',
|
|
50
|
-
where,
|
|
51
|
-
// orderBy,
|
|
52
|
-
// limit,
|
|
53
|
-
action: entities => this.absorb(entities, this.idField),
|
|
54
|
-
confirm,
|
|
55
|
-
notify,
|
|
56
|
-
slience
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
48
|
|
|
60
49
|
|
|
61
|
-
async
|
|
62
|
-
|
|
63
|
-
|
|
50
|
+
async selectIn<F extends strKeyOf<R>>(
|
|
51
|
+
field: F,
|
|
52
|
+
values: R[F][],
|
|
53
|
+
{ where, custom, confirm, notify, slience, patch }: selectOptions<R> = {},
|
|
64
54
|
chunkSize = 100,
|
|
65
55
|
) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
56
|
+
if (!patch) this.clear()
|
|
57
|
+
let getSelect = (ck: R[F][]): Promise<void> => this.select({
|
|
58
|
+
where: { [field]: ck, ...where },
|
|
59
|
+
custom,
|
|
60
|
+
confirm,
|
|
61
|
+
notify,
|
|
62
|
+
slience,
|
|
63
|
+
patch: true
|
|
64
|
+
})
|
|
65
|
+
await Promise.all(chunk(values, chunkSize).map(getSelect))
|
|
74
66
|
if (this.ordering.length === 0)
|
|
75
|
-
this.
|
|
67
|
+
this.order({ [field]: values } as any)
|
|
76
68
|
}
|
|
77
69
|
|
|
78
70
|
|
|
79
|
-
async selectIDs(
|
|
80
|
-
ids: R[this['idField']][],
|
|
81
|
-
{ where, confirm, notify, slience }: selectOptions<R> = {},
|
|
82
|
-
chunkSize = 100,
|
|
83
|
-
) {
|
|
84
|
-
this.clear()
|
|
85
|
-
this.patchIDs(
|
|
86
|
-
ids,
|
|
87
|
-
{ where, confirm, notify, slience },
|
|
88
|
-
chunkSize
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
71
|
|
|
93
72
|
}
|
|
94
73
|
|
package/src/utils/supabase.ts
CHANGED
|
@@ -137,7 +137,7 @@ export function handleError<T>(data: T[] | null, error: PostgrestError | null):
|
|
|
137
137
|
|
|
138
138
|
|
|
139
139
|
|
|
140
|
-
const _filterBuilder = supabase.from('DEMO').select()
|
|
140
|
+
const _filterBuilder = supabase.from('DEMO').select().eq('','').order('f').limit(3)
|
|
141
141
|
export type PostgrestFilterBuilder = typeof _filterBuilder
|
|
142
142
|
|
|
143
143
|
|