pinstripe 0.31.0 → 0.32.0
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/lib/background_job.js +42 -0
- package/lib/background_jobs/_file_importer.js +1 -0
- package/lib/background_jobs/purge_used_hashes.js +12 -0
- package/lib/command.js +0 -12
- package/lib/commands/generate_background_job.js +47 -0
- package/lib/commands/generate_project.js +1 -1
- package/lib/commands/list_background_jobs.js +15 -0
- package/lib/commands/run_background_job.js +11 -0
- package/lib/component.js +7 -4
- package/lib/components/helpers.js +18 -6
- package/lib/components/pinstripe_document.js +6 -2
- package/lib/components/pinstripe_frame.js +3 -3
- package/lib/components/pinstripe_modal.js +26 -7
- package/lib/database/client.js +65 -0
- package/lib/database/constants.js +13 -1
- package/lib/database/row.js +18 -23
- package/lib/database/table.js +107 -107
- package/lib/database.js +10 -2
- package/lib/defer.js +10 -10
- package/lib/defer.test.js +11 -0
- package/lib/extensions/multi-tenant/database/row.js +22 -0
- package/lib/extensions/multi-tenant/models/_file_importer.js +1 -0
- package/lib/extensions/multi-tenant/models/tenant.js +14 -0
- package/lib/extensions/multi-tenant/services/database.js +4 -1
- package/lib/extensions/multi-tenant/services/run_background_job.js +27 -0
- package/lib/index.js +1 -0
- package/lib/inflector.js +4 -0
- package/lib/markdown.js +13 -5
- package/lib/migrations/1708772281_create_used_hash.js +9 -0
- package/lib/migrations/_file_importer.js +2 -0
- package/lib/models/_file_importer.js +2 -0
- package/lib/models/used_hash.js +7 -0
- package/lib/proof_of_work.js +7 -16
- package/lib/registry.js +2 -3
- package/lib/services/bot.js +9 -9
- package/lib/services/only_once.js +24 -0
- package/lib/services/render_form.js +15 -8
- package/lib/services/render_table.js +48 -0
- package/lib/services/run_background_job.js +8 -0
- package/lib/views/docs/docs/guides/introduction.md +1 -1
- package/lib/views/shared/_button.js +51 -11
- package/lib/views/shared/_danger_area.js +78 -0
- package/lib/views/shared/_editable_area.js +4 -4
- package/lib/views/shared/_form.js +53 -8
- package/lib/views/shared/_pagination.js +47 -0
- package/lib/views/shared/_panel.js +4 -0
- package/lib/views/shared/_section.js +4 -0
- package/lib/views/shared/_shell/index.js +2 -1
- package/lib/views/shared/_table.js +139 -0
- package/package.json +3 -4
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
import { Class } from './class.js';
|
|
3
|
+
import { inflector } from './inflector.js';
|
|
4
|
+
import { Registry } from './registry.js';
|
|
5
|
+
import { ServiceConsumer } from './service_consumer.js';
|
|
6
|
+
|
|
7
|
+
export const BackgroundJob = Class.extend().include({
|
|
8
|
+
meta(){
|
|
9
|
+
this.assignProps({ name: 'BackgroundJob' });
|
|
10
|
+
|
|
11
|
+
this.include(Registry);
|
|
12
|
+
this.include(ServiceConsumer);
|
|
13
|
+
|
|
14
|
+
this.assignProps({
|
|
15
|
+
normalizeName(name){
|
|
16
|
+
return inflector.dasherize(name);
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
get schedules(){
|
|
20
|
+
if(!this.hasOwnProperty('_schedules')){
|
|
21
|
+
this._schedules = [];
|
|
22
|
+
}
|
|
23
|
+
return this._schedules;
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
schedule(...args){
|
|
27
|
+
this.schedules.push(args);
|
|
28
|
+
return this;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
async run(context, name){
|
|
32
|
+
await context.fork().run(async context => {
|
|
33
|
+
await this.create(name, context).run();
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
run(){
|
|
40
|
+
console.error(`No such background job "${this.constructor.name}" exists.`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BackgroundJob as default } from 'pinstripe';
|
package/lib/command.js
CHANGED
|
@@ -16,18 +16,6 @@ export const Command = Class.extend().include({
|
|
|
16
16
|
return inflector.dasherize(name);
|
|
17
17
|
},
|
|
18
18
|
|
|
19
|
-
get schedules(){
|
|
20
|
-
if(!this.hasOwnProperty('_schedules')){
|
|
21
|
-
this._schedules = [];
|
|
22
|
-
}
|
|
23
|
-
return this._schedules;
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
schedule(...args){
|
|
27
|
-
this.schedules.push(args);
|
|
28
|
-
return this;
|
|
29
|
-
},
|
|
30
|
-
|
|
31
19
|
async run(context, name = 'list-commands', ...args){
|
|
32
20
|
await context.fork().run(async context => {
|
|
33
21
|
context.args = [ ...args ];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
async run(){
|
|
5
|
+
const [ name = '' ] = this.args;
|
|
6
|
+
const normalizedName = this.inflector.snakeify(name);
|
|
7
|
+
if(normalizedName == ''){
|
|
8
|
+
console.error('A background_job name must be given.');
|
|
9
|
+
process.exit();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { inProjectRootDir, generateFile, line, indent } = this.fsBuilder;
|
|
13
|
+
|
|
14
|
+
await inProjectRootDir(async () => {
|
|
15
|
+
|
|
16
|
+
await generateFile(`lib/background_jobs/_file_importer.js`, { skipIfExists: true }, () => {
|
|
17
|
+
line();
|
|
18
|
+
line(`export { BackgroundJob as default } from 'pinstripe';`);
|
|
19
|
+
line();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await generateFile(`lib/background_jobs/${normalizedName}.js`, () => {
|
|
23
|
+
line();
|
|
24
|
+
line(`export default {`);
|
|
25
|
+
indent(() => {
|
|
26
|
+
line('meta(){');
|
|
27
|
+
indent(() => {
|
|
28
|
+
line(`this.schedule('* * * * *'); // run every minute`);
|
|
29
|
+
});
|
|
30
|
+
line('}');
|
|
31
|
+
});
|
|
32
|
+
line();
|
|
33
|
+
indent(() => {
|
|
34
|
+
line('run(){');
|
|
35
|
+
indent(() => {
|
|
36
|
+
line(`console.log('${this.inflector.dasherize(normalizedName)} background job coming soon!')`);
|
|
37
|
+
});
|
|
38
|
+
line('}');
|
|
39
|
+
});
|
|
40
|
+
line('};');
|
|
41
|
+
line();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { BackgroundJob } from 'pinstripe';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
run(){
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log('The following background jobs are available:');
|
|
9
|
+
console.log('');
|
|
10
|
+
BackgroundJob.names.forEach(backgroundJobName => {
|
|
11
|
+
console.log(` * ${chalk.green(backgroundJobName)}`);
|
|
12
|
+
});
|
|
13
|
+
console.log('');
|
|
14
|
+
}
|
|
15
|
+
};
|
package/lib/component.js
CHANGED
|
@@ -203,7 +203,7 @@ export const Component = Class.extend().include({
|
|
|
203
203
|
},
|
|
204
204
|
|
|
205
205
|
get isInput(){
|
|
206
|
-
return this.is('input, textarea');
|
|
206
|
+
return this.is('input, textarea, select');
|
|
207
207
|
},
|
|
208
208
|
|
|
209
209
|
get name(){
|
|
@@ -220,6 +220,9 @@ export const Component = Class.extend().include({
|
|
|
220
220
|
if(this.is('input[type="checkbox"]')){
|
|
221
221
|
return this.is(':checked') ? true : false;
|
|
222
222
|
}
|
|
223
|
+
if(this.is('select')){
|
|
224
|
+
return this.findAll('option').map(option => option.value)[this.node.selectedIndex];
|
|
225
|
+
}
|
|
223
226
|
return this.node.value;
|
|
224
227
|
},
|
|
225
228
|
|
|
@@ -597,14 +600,14 @@ function patchAttributes(attributes){
|
|
|
597
600
|
if(attributes[key] === undefined){
|
|
598
601
|
Element.prototype.removeAttribute.call(this.node, key); // work around for https://github.com/cypress-io/cypress/issues/26206
|
|
599
602
|
// this.node.removeAttribute(key);
|
|
603
|
+
if(key == 'checked') this.node.checked = false;
|
|
600
604
|
}
|
|
601
605
|
})
|
|
602
606
|
Object.keys(attributes).forEach((key) => {
|
|
603
607
|
if(!currentAttributes.hasOwnProperty(key) || currentAttributes[key] != attributes[key]){
|
|
604
608
|
this.node.setAttribute(key, attributes[key]);
|
|
605
|
-
if(key == 'value')
|
|
606
|
-
|
|
607
|
-
}
|
|
609
|
+
if(key == 'value') this.node.value = attributes[key];
|
|
610
|
+
if(key == 'checked') this.node.checked = true;
|
|
608
611
|
}
|
|
609
612
|
})
|
|
610
613
|
}
|
|
@@ -51,11 +51,23 @@ export function normalizeUrl(url, referenceUrl = window.location){
|
|
|
51
51
|
const matches = `${url}`.match(/^&(.*)$/);
|
|
52
52
|
const out = matches ? new URL(referenceUrl) : new URL(url, referenceUrl);
|
|
53
53
|
if(matches){
|
|
54
|
-
|
|
55
|
-
out.search
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
54
|
+
out.search = `?${stringifyUrlSearch({
|
|
55
|
+
...parseUrlSearch(out.search),
|
|
56
|
+
...parseUrlSearch(`${matches[1]}`)
|
|
57
|
+
})}`;
|
|
59
58
|
}
|
|
60
59
|
return out;
|
|
61
|
-
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseUrlSearch(search){
|
|
63
|
+
const out = {};
|
|
64
|
+
search.replace(/^\?/, '').split('&').forEach(pair => {
|
|
65
|
+
const [key, value] = pair.split('=');
|
|
66
|
+
out[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
67
|
+
});
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function stringifyUrlSearch(search){
|
|
72
|
+
return Object.keys(search).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(search[key])}`).join('&');
|
|
73
|
+
}
|
|
@@ -47,6 +47,10 @@ export default {
|
|
|
47
47
|
return this.body.progressBar;
|
|
48
48
|
},
|
|
49
49
|
|
|
50
|
+
get loadCacheNamespace(){
|
|
51
|
+
return this.head.find('meta[name="pinstripe-load-cache-namespace"]')?.params.content ?? 'default';
|
|
52
|
+
},
|
|
53
|
+
|
|
50
54
|
async load(url = this.url.toString(), options = {}){
|
|
51
55
|
const { replace, method = 'GET' } = options;
|
|
52
56
|
const previousUrl = this.url.toString();
|
|
@@ -65,12 +69,12 @@ export default {
|
|
|
65
69
|
},
|
|
66
70
|
|
|
67
71
|
async preload(url){
|
|
68
|
-
if(loadCache.get(url
|
|
72
|
+
if(loadCache.get(`${this.document.loadCacheNamespace}:${url}`)) return;
|
|
69
73
|
if(preloading[url.toString()]) return;
|
|
70
74
|
preloading[url.toString()] = true;
|
|
71
75
|
const response = await fetch(url);
|
|
72
76
|
const html = await response.text();
|
|
73
|
-
loadCache.put(url
|
|
77
|
+
loadCache.put(`${this.document.loadCacheNamespace}:${url}`, html);
|
|
74
78
|
delete preloading[url.toString()];
|
|
75
79
|
}
|
|
76
80
|
};
|
|
@@ -47,11 +47,11 @@ export default {
|
|
|
47
47
|
this.loading = true;
|
|
48
48
|
this.abort();
|
|
49
49
|
const { method = 'GET', placeholderUrl } = options;
|
|
50
|
-
const cachedHtml = method == 'GET' ? loadCache.get(url
|
|
50
|
+
const cachedHtml = method == 'GET' ? loadCache.get(`${this.document.loadCacheNamespace}:${url}`) : undefined;
|
|
51
51
|
if(cachedHtml) this.patch(cachedHtml);
|
|
52
52
|
let minimumDelay = 0;
|
|
53
53
|
if(!cachedHtml && placeholderUrl){
|
|
54
|
-
const placeholderHtml = loadCache.get(placeholderUrl
|
|
54
|
+
const placeholderHtml = loadCache.get(`${this.document.loadCacheNamespace}:${placeholderUrl}`);
|
|
55
55
|
if(placeholderHtml) {
|
|
56
56
|
this.patch(placeholderHtml);
|
|
57
57
|
minimumDelay = 300;
|
|
@@ -62,7 +62,7 @@ export default {
|
|
|
62
62
|
this.loading = false;
|
|
63
63
|
if(html == cachedHtml && !this.loadWasBlocked) return;
|
|
64
64
|
this.loadWasBlocked = false;
|
|
65
|
-
if(method == 'GET') loadCache.put(this.url.toString(), html);
|
|
66
65
|
this.patch(html);
|
|
66
|
+
if(method == 'GET') loadCache.put(`${this.document.loadCacheNamespace}:${this.url}`, html);
|
|
67
67
|
}
|
|
68
68
|
};
|
|
@@ -3,6 +3,8 @@ export default {
|
|
|
3
3
|
initialize(...args){
|
|
4
4
|
this.constructor.parent.prototype.initialize.call(this, ...args);
|
|
5
5
|
|
|
6
|
+
const { width = 'medium', height = 'auto' } = this.params;
|
|
7
|
+
|
|
6
8
|
this.shadow.patch(`
|
|
7
9
|
<style>
|
|
8
10
|
.root {
|
|
@@ -58,20 +60,37 @@ export default {
|
|
|
58
60
|
height: 50%;
|
|
59
61
|
width: 0.2rem;
|
|
60
62
|
}
|
|
63
|
+
.container {
|
|
64
|
+
min-height: calc(100vh - 4.0rem);
|
|
65
|
+
width: calc(100vw - 16.0rem);
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: center;
|
|
69
|
+
}
|
|
61
70
|
.body {
|
|
62
|
-
max-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
max-width: 100%;
|
|
72
|
+
}
|
|
73
|
+
.root.is-medium-width .body {
|
|
74
|
+
width: 800px;
|
|
75
|
+
}
|
|
76
|
+
.root.is-full-width .body {
|
|
77
|
+
width: 100%;
|
|
78
|
+
}
|
|
79
|
+
.root.is-full-height .body {
|
|
80
|
+
height: 100%;
|
|
66
81
|
}
|
|
67
82
|
</style>
|
|
68
|
-
<div class="root">
|
|
83
|
+
<div class="root is-${width}-width is-${height}-height">
|
|
69
84
|
<button class="close-button"></button>
|
|
70
|
-
<div class="
|
|
85
|
+
<div class="container">
|
|
86
|
+
<div class="body">
|
|
87
|
+
<slot>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
71
90
|
</div>
|
|
72
91
|
`);
|
|
73
92
|
|
|
74
|
-
this.shadow.on('click', '.root, .close-button', () => this.trigger('close'));
|
|
93
|
+
this.shadow.on('click', '.root, .container, .body, .close-button', () => this.trigger('close'));
|
|
75
94
|
},
|
|
76
95
|
|
|
77
96
|
isModal: true
|
package/lib/database/client.js
CHANGED
|
@@ -4,6 +4,8 @@ import { existsSync, unlinkSync } from 'fs';
|
|
|
4
4
|
|
|
5
5
|
import { Class } from '../class.js';
|
|
6
6
|
|
|
7
|
+
import { MYSQL_COLUMN_TYPE_TO_TYPE_MAP, SQLITE_COLUMN_TYPE_TO_TYPE_MAP } from './constants.js';
|
|
8
|
+
|
|
7
9
|
let sqliteConnectionCounters = {};
|
|
8
10
|
let sqliteConnections = {};
|
|
9
11
|
|
|
@@ -143,6 +145,69 @@ export const Client = Class.extend().include({
|
|
|
143
145
|
const fn = alternatives[adapter];
|
|
144
146
|
if(!fn) throw new Error(`"${adapter}" adapter not supported.`);
|
|
145
147
|
return fn.call(that);
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
async extractSchema(){
|
|
151
|
+
const out = {};
|
|
152
|
+
|
|
153
|
+
const tableNames = await this.adapt(this, {
|
|
154
|
+
async mysql(){
|
|
155
|
+
const rows = await this.run('show tables');
|
|
156
|
+
return rows.map(row => Object.values(row)[0]);
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
async sqlite(){
|
|
160
|
+
const rows = await this.run(`select name from sqlite_schema where type ='table' and name not like 'sqlite_%'`);
|
|
161
|
+
return rows.map(({ name }) => name);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
while(tableNames.length){
|
|
166
|
+
const tableName = tableNames.shift();
|
|
167
|
+
const columns = await this.adapt(this, {
|
|
168
|
+
async mysql(){
|
|
169
|
+
const out = {};
|
|
170
|
+
const rows = await this.run(`describe \`${tableName}\``);
|
|
171
|
+
rows.forEach(row => {
|
|
172
|
+
const name = row['Field'];
|
|
173
|
+
let type;
|
|
174
|
+
if(name == '_id'){
|
|
175
|
+
type = 'primary_key';
|
|
176
|
+
} else if(name == 'id'){
|
|
177
|
+
type = 'alternate_key';
|
|
178
|
+
} else {
|
|
179
|
+
type = MYSQL_COLUMN_TYPE_TO_TYPE_MAP[row['Type']] || 'string';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
out[name] = type;
|
|
183
|
+
});
|
|
184
|
+
return out;
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
async sqlite(){
|
|
188
|
+
const out = {};
|
|
189
|
+
const rows = await this.run(`pragma table_info(\`${tableName}\`)`);
|
|
190
|
+
rows.forEach(row => {
|
|
191
|
+
const { name } = row;
|
|
192
|
+
let type;
|
|
193
|
+
if(name == '_id'){
|
|
194
|
+
type = 'primary_key';
|
|
195
|
+
} else if(name == 'id'){
|
|
196
|
+
type = 'alternate_key';
|
|
197
|
+
} else if(name.match(/.+Id$/)){
|
|
198
|
+
type = 'foreign_key';
|
|
199
|
+
} else {
|
|
200
|
+
type = SQLITE_COLUMN_TYPE_TO_TYPE_MAP[row.type] || 'string';
|
|
201
|
+
}
|
|
202
|
+
out[name] = type;
|
|
203
|
+
});
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
out[tableName] = columns;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return out;
|
|
146
211
|
}
|
|
147
212
|
});
|
|
148
213
|
|
|
@@ -46,7 +46,7 @@ export const SQLITE_COLUMN_TYPE_TO_TYPE_MAP = (() => {
|
|
|
46
46
|
return out;
|
|
47
47
|
})();
|
|
48
48
|
|
|
49
|
-
export const
|
|
49
|
+
export const MYSQL_COMPARISON_OPERATORS = {
|
|
50
50
|
'': '? = ?',
|
|
51
51
|
Ne: '? != ?',
|
|
52
52
|
Lt: '? < ?',
|
|
@@ -58,6 +58,18 @@ export const COMPARISON_OPERATORS = {
|
|
|
58
58
|
Contains: `? like concat('%', ?, '%')`
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
export const SQLITE_COMPARISON_OPERATORS = {
|
|
62
|
+
'': '? = ?',
|
|
63
|
+
Ne: '? != ?',
|
|
64
|
+
Lt: '? < ?',
|
|
65
|
+
Gt: '? > ?',
|
|
66
|
+
Le: '? <= ?',
|
|
67
|
+
Ge: '? >= ?',
|
|
68
|
+
BeginsWith: `? like (? || '%')`,
|
|
69
|
+
EndsWith: `? like ('%' || ?)`,
|
|
70
|
+
Contains: `? like ('%' || ? || '%')`
|
|
71
|
+
};
|
|
72
|
+
|
|
61
73
|
export const MYSQL_KEY_COMPARISON_OPERATORS = {
|
|
62
74
|
'': '? = uuid_to_bin(?)',
|
|
63
75
|
Ne: '? != uuid_to_bin(?)'
|
package/lib/database/row.js
CHANGED
|
@@ -18,15 +18,6 @@ export const Row = Model.extend().include({
|
|
|
18
18
|
defineCallbacks.call(this, 'beforeInsert', 'afterInsert', 'beforeUpdate', 'afterUpdate', 'beforeDelete', 'afterDelete')
|
|
19
19
|
|
|
20
20
|
this.assignProps({
|
|
21
|
-
|
|
22
|
-
async loadSchema(client){
|
|
23
|
-
await Table.loadSchema(client);
|
|
24
|
-
this.clearCache();
|
|
25
|
-
this.names.forEach(name => this.for(name));
|
|
26
|
-
Table.clearCache();
|
|
27
|
-
Table.names.forEach(name => Table.for(name));
|
|
28
|
-
},
|
|
29
|
-
|
|
30
21
|
normalizeName(name){
|
|
31
22
|
return inflector.camelize(name);
|
|
32
23
|
},
|
|
@@ -61,11 +52,7 @@ export const Row = Model.extend().include({
|
|
|
61
52
|
includeInTable(...args){
|
|
62
53
|
if(this.abstract) return;
|
|
63
54
|
|
|
64
|
-
Table.
|
|
65
|
-
meta(){
|
|
66
|
-
this.include(...args);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
55
|
+
Table.for(this.collectionName).include(...args);
|
|
69
56
|
},
|
|
70
57
|
|
|
71
58
|
scope(...args){
|
|
@@ -110,6 +97,16 @@ export const Row = Model.extend().include({
|
|
|
110
97
|
cascadeDelete: false,
|
|
111
98
|
...options
|
|
112
99
|
});
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
mustBeUnique(name, options = {}){
|
|
103
|
+
const { message = 'Must be unique', collection = this.collectionName } = options;
|
|
104
|
+
return this.validateWith(async row => {
|
|
105
|
+
if(row.isValidationError(name)) return;
|
|
106
|
+
const value = row[name];
|
|
107
|
+
const alreadyExists = await row.database[collection].where({ [name]: value, idNe: row.id }).count() > 0;
|
|
108
|
+
if(alreadyExists) row.setValidationError(name, message);
|
|
109
|
+
});
|
|
113
110
|
}
|
|
114
111
|
});
|
|
115
112
|
},
|
|
@@ -153,13 +150,15 @@ export const Row = Model.extend().include({
|
|
|
153
150
|
modifiedFields[name] = this[name];
|
|
154
151
|
}
|
|
155
152
|
});
|
|
153
|
+
|
|
154
|
+
const exists = this._exists;
|
|
156
155
|
|
|
157
156
|
if(Object.keys(modifiedFields).length) {
|
|
158
157
|
const query = [];
|
|
159
158
|
|
|
160
159
|
const tableReference = TableReference.new(this.constructor.collectionName);
|
|
161
160
|
|
|
162
|
-
if(
|
|
161
|
+
if(exists){
|
|
163
162
|
query.push('update ? set ', tableReference);
|
|
164
163
|
Object.keys(modifiedFields).forEach((name, i) => {
|
|
165
164
|
if(name == 'id') return;
|
|
@@ -186,7 +185,6 @@ export const Row = Model.extend().include({
|
|
|
186
185
|
query.push(' where ? = ?', tableReference.createColumnReference('id'), this._initialFields.id);
|
|
187
186
|
}
|
|
188
187
|
});
|
|
189
|
-
|
|
190
188
|
} else {
|
|
191
189
|
query.push('insert into ?(', tableReference);
|
|
192
190
|
Object.keys(modifiedFields).forEach((name, i) => {
|
|
@@ -227,10 +225,9 @@ export const Row = Model.extend().include({
|
|
|
227
225
|
});
|
|
228
226
|
|
|
229
227
|
this._exists = true;
|
|
230
|
-
|
|
231
228
|
}
|
|
232
229
|
|
|
233
|
-
await (
|
|
230
|
+
await (exists ? this._runAfterUpdateCallbacks() : this._runAfterInsertCallbacks());
|
|
234
231
|
|
|
235
232
|
return this;
|
|
236
233
|
});
|
|
@@ -272,10 +269,7 @@ export const Row = Model.extend().include({
|
|
|
272
269
|
const names = [...new Set([ ...Object.keys(columns), ...extractSettableProps(this) ])];
|
|
273
270
|
while(names.length){
|
|
274
271
|
const name = names.shift();
|
|
275
|
-
|
|
276
|
-
if(typeof value?.toFieldValue == 'function'){
|
|
277
|
-
value = await value.toFieldValue();
|
|
278
|
-
}
|
|
272
|
+
const value = await this[name];
|
|
279
273
|
const type = columns[name] ? columns[name] : typeof value;
|
|
280
274
|
fields.push({
|
|
281
275
|
name,
|
|
@@ -300,7 +294,8 @@ export const Row = Model.extend().include({
|
|
|
300
294
|
|
|
301
295
|
function defineRelationship({ name, type, collectionName, fromKey, toKey, cascadeDelete, through }){
|
|
302
296
|
if(this.abstract) return;
|
|
303
|
-
|
|
297
|
+
|
|
298
|
+
this.includeInTable({
|
|
304
299
|
meta(){
|
|
305
300
|
this.prototype.assignProps({
|
|
306
301
|
get [name](){
|