pinstripe 0.12.0 → 0.15.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.
Files changed (86) hide show
  1. package/cli.js +1 -1
  2. package/lib/command.js +12 -0
  3. package/lib/commands/generate_project.js +18 -0
  4. package/lib/commands/generate_static_site.js +8 -8
  5. package/lib/commands/generate_view.js +4 -3
  6. package/lib/commands/list_commands.js +2 -4
  7. package/lib/commands/list_migrations.js +2 -4
  8. package/lib/commands/list_models.js +2 -4
  9. package/lib/commands/list_node_wrappers.js +2 -4
  10. package/lib/commands/list_services.js +2 -4
  11. package/lib/commands/list_views.js +2 -4
  12. package/lib/commands/purge_old_sessions.js +12 -0
  13. package/lib/commands/start_server.js +10 -8
  14. package/lib/database/adapters/mysql.js +55 -11
  15. package/lib/database/adapters/sqlite.js +87 -28
  16. package/lib/database/constants.js +0 -4
  17. package/lib/database/row.js +5 -3
  18. package/lib/database/table.js +13 -5
  19. package/lib/database/union.js +13 -6
  20. package/lib/database.js +12 -1
  21. package/lib/extensions/_importer.js +2 -0
  22. package/lib/extensions/multi-tenant/database/table.js +18 -0
  23. package/lib/extensions/multi-tenant/database.js +6 -0
  24. package/lib/extensions/multi-tenant/index.js +4 -0
  25. package/lib/extensions/multi-tenant/migrations/1627976174_create_tenant_table_and_add_tenant_id_to_existing_tables.js +16 -0
  26. package/lib/extensions/multi-tenant/migrations/_importer.js +2 -0
  27. package/lib/extensions/multi-tenant/models/_importer.js +2 -0
  28. package/lib/extensions/multi-tenant/models/tenant.js +7 -0
  29. package/lib/extensions/multi-tenant/services/_importer.js +2 -0
  30. package/lib/extensions/multi-tenant/services/tenant.js +7 -0
  31. package/lib/import_all.js +1 -0
  32. package/lib/initialize.client.js +4 -27
  33. package/lib/migrations/1628057822_create_session.js +1 -0
  34. package/lib/node_wrapper.js +55 -6
  35. package/lib/node_wrappers/anchor.client.js +2 -0
  36. package/lib/node_wrappers/document.client.js +1 -1
  37. package/lib/node_wrappers/form.client.js +12 -1
  38. package/lib/node_wrappers/helpers.js +2 -2
  39. package/lib/node_wrappers/markdown_editor/line_inserter.client.js +1 -1
  40. package/lib/node_wrappers/markdown_editor.client.js +9 -2
  41. package/lib/node_wrappers/overlay.client.js +15 -2
  42. package/lib/services/app.js +2 -0
  43. package/lib/services/bot.js +69 -0
  44. package/lib/services/command_names.js +6 -0
  45. package/lib/services/config.js +0 -1
  46. package/lib/services/fetch.js +15 -5
  47. package/lib/services/format_date.js +6 -0
  48. package/lib/services/migration_names.js +6 -0
  49. package/lib/services/model_names.js +6 -0
  50. package/lib/services/node_wrapper_names.js +6 -0
  51. package/lib/services/params.js +6 -1
  52. package/lib/services/render_form.js +71 -63
  53. package/lib/services/render_frame.js +11 -0
  54. package/lib/services/render_list.js +3 -3
  55. package/lib/services/render_markdown.js +1 -1
  56. package/lib/services/render_table.js +8 -8
  57. package/lib/services/send_mail.js +32 -0
  58. package/lib/services/service_names.js +6 -0
  59. package/lib/services/session.js +8 -2
  60. package/lib/services/stylesheets.js +16 -0
  61. package/lib/services/view_names.js +6 -0
  62. package/lib/url.js +2 -2
  63. package/lib/views/{_layout.js → main/_layout.js} +4 -2
  64. package/lib/views/{blocks → main/blocks}/default.js +6 -8
  65. package/lib/views/{blocks → main/blocks}/help.js +0 -0
  66. package/lib/views/{bundle.js.js → main/bundle.js.js} +1 -1
  67. package/lib/views/{sign_in.js → main/sign_in.js} +2 -6
  68. package/lib/views/{sign_out.js → main/sign_out.js} +0 -0
  69. package/lib/views/main/stylesheets/components/button.css.js +56 -0
  70. package/lib/views/main/stylesheets/components/card.css.js +41 -0
  71. package/lib/views/main/stylesheets/components/form.css.js +11 -0
  72. package/lib/views/main/stylesheets/components/frame.css.js +10 -0
  73. package/lib/views/main/stylesheets/components/input.css.js +54 -0
  74. package/lib/views/main/stylesheets/components/label.css.js +14 -0
  75. package/lib/views/main/stylesheets/components/markdown_editor.css.js +53 -0
  76. package/lib/views/main/stylesheets/components/modal.css.js +77 -0
  77. package/lib/views/main/stylesheets/components/overlay.css.js +17 -0
  78. package/lib/views/main/stylesheets/components/pagination.css.js +80 -0
  79. package/lib/views/main/stylesheets/components/progress_bar.css.js +32 -0
  80. package/lib/views/main/stylesheets/components/textarea.css.js +17 -0
  81. package/lib/views/main/stylesheets/global.css +120 -0
  82. package/lib/views/main/stylesheets/reset.css +74 -0
  83. package/lib/views/main/stylesheets/vars.css +25 -0
  84. package/package.json +8 -5
  85. package/pinstripe_development.db +0 -0
  86. package/lib/views/base.css.js +0 -657
package/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node --unhandled-rejections=strict
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { spawn } from 'child_process';
4
4
 
package/lib/command.js CHANGED
@@ -19,6 +19,18 @@ export const Command = Base.extend().include({
19
19
 
20
20
  run(name = 'list-commands', ...args){
21
21
  return this.create(name, ...args).run();
22
+ },
23
+
24
+ get schedules(){
25
+ if(!this.hasOwnProperty('_schedules')){
26
+ this._schedules = [];
27
+ }
28
+ return this._schedules;
29
+ },
30
+
31
+ schedule(...args){
32
+ this.schedules.push(args);
33
+ return this;
22
34
  }
23
35
  });
24
36
  },
@@ -36,11 +36,29 @@ export default async ({
36
36
  await generateFile(`lib/index.js`, () => {
37
37
  line();
38
38
  line(`import { importAll } from 'pinstripe';`);
39
+ dependencies.forEach((dependency) => {
40
+ if(dependency != 'pinstripe') line(`import '${dependency}';`);
41
+ });
39
42
  line();
40
43
  line(`importAll(import.meta.url);`);
41
44
  line();
42
45
  });
43
46
 
47
+ await generateFile(`README.md`, () => {
48
+ line();
49
+ line(`# ${name}`);
50
+ line();
51
+ line('## Getting started');
52
+ line();
53
+ line('```bash');
54
+ indent(() => {
55
+ line('pinstripe init-database');
56
+ line('pinstripe start-server');
57
+ });
58
+ line('```');
59
+ line();
60
+ });
61
+
44
62
  spawnSync('yarn', [ 'add', ...dependencies ], {
45
63
  stdio: 'inherit'
46
64
  });
@@ -8,12 +8,12 @@ export default {
8
8
 
9
9
  async run(){
10
10
  this.pages = {};
11
- const paths = Object.keys(View.classes).filter(path => !path.match(/(^|\/)_/)).map(path => {
12
- return `/${path.replace(/(^|\/)index$/, '')}`.replace(/^\/+/, '/');
11
+ const urls = Object.keys(View.classes).filter(path => !path.match(/(^|\/)_/)).map(path => {
12
+ return Url.fromString(`/${path.replace(/(^|\/)index$/, '')}`.replace(/^\/+/, '/'));
13
13
  });
14
14
 
15
- while(paths.length){
16
- await this.crawlPage({ _path: paths.shift() });
15
+ while(urls.length){
16
+ await this.crawlPage({ _url: urls.shift() });
17
17
  }
18
18
 
19
19
  const pages = Object.values(this.pages).filter(page => page.status == 200 && Object.keys(page.params).length == 1);
@@ -25,7 +25,7 @@ export default {
25
25
  const { params, headers } = pages.shift();
26
26
  const contentType = headers['content-type'];
27
27
 
28
- const path = params._path;
28
+ const path = params._url.path;
29
29
  let filePath = path.replace(/^\//, '');
30
30
  if(filePath.match(/(^|\/)$/)){
31
31
  filePath = `${filePath}index`;
@@ -34,7 +34,7 @@ export default {
34
34
  filePath = `${filePath}.${mimeTypes.extension(contentType)}`
35
35
  }
36
36
 
37
- const data = (await this.fetch({ _path: path }))[2];
37
+ const data = (await this.fetch({ _url: Url.fromString(path) }))[2];
38
38
 
39
39
  await generateFile(filePath, () => {
40
40
  echo(data.join(''));
@@ -58,7 +58,7 @@ export default {
58
58
  const urls = this.extractUrls(virtualDom);
59
59
  while(urls.length){
60
60
  const url = urls.shift();
61
- await this.crawlPage({ ...url.params, _path: url.path });
61
+ await this.crawlPage({ ...url.params, _url: url });
62
62
  }
63
63
  },
64
64
 
@@ -68,7 +68,7 @@ export default {
68
68
  ['src', 'href'].forEach(name => {
69
69
  const value = attributes[name];
70
70
  if(!value) return;
71
- const url = Url.fromString(value, 'http://localhost/');
71
+ const url = Url.fromString(value);
72
72
  if(url.host != 'localhost') return;
73
73
  out.push(url);
74
74
  });
@@ -1,7 +1,8 @@
1
1
 
2
2
  export default async ({
3
3
  cliUtils: { extractArg },
4
- fsBuilder: { inProjectRootDir, generateFile, line, indent }
4
+ fsBuilder: { inProjectRootDir, generateFile, line, indent },
5
+ app
5
6
  }) => {
6
7
  let name = extractArg('').replace(/^\//, '');
7
8
  if(name == ''){
@@ -10,7 +11,7 @@ export default async ({
10
11
  }
11
12
 
12
13
  if(!name.match(/\.[^\/]+$/)){
13
- name = `${name}.tpl.js`;
14
+ name = `${name}.js`;
14
15
  }
15
16
 
16
17
  await inProjectRootDir(async () => {
@@ -21,7 +22,7 @@ export default async ({
21
22
  line();
22
23
  });
23
24
 
24
- await generateFile(`lib/views/${name}`, () => {
25
+ await generateFile(`lib/views/${await app}/${name}`, () => {
25
26
  line();
26
27
  line('export default ({ renderHtml, params }) => renderHtml(`');
27
28
  indent(() => {
@@ -1,13 +1,11 @@
1
1
 
2
2
  import chalk from 'chalk';
3
3
 
4
- import { Command } from '../command.js';
5
-
6
- export default () => {
4
+ export default ({ commandNames }) => {
7
5
  console.log('');
8
6
  console.log('The following commands are available:');
9
7
  console.log('');
10
- Object.keys(Command.classes).sort().forEach(commandName => {
8
+ commandNames.forEach(commandName => {
11
9
  console.log(` * ${chalk.green(commandName)}`);
12
10
  });
13
11
  console.log('');
@@ -1,13 +1,11 @@
1
1
 
2
2
  import chalk from 'chalk';
3
3
 
4
- import { Migration } from '../database/migration.js'
5
-
6
- export default () => {
4
+ export default ({ migrationNames }) => {
7
5
  console.log('');
8
6
  console.log('The following migrations are available:');
9
7
  console.log('');
10
- Object.keys(Migration.classes).sort().forEach(migrationName => {
8
+ migrationNames.forEach(migrationName => {
11
9
  console.log(` * ${chalk.green(migrationName)}`);
12
10
  });
13
11
  console.log('');
@@ -1,13 +1,11 @@
1
1
 
2
2
  import chalk from 'chalk';
3
3
 
4
- import { Row } from '../database/row.js';
5
-
6
- export default () => {
4
+ export default ({ modelNames }) => {
7
5
  console.log('');
8
6
  console.log('The following models are available:');
9
7
  console.log('');
10
- Object.keys(Row.classes).sort().forEach(modelName => {
8
+ modelNames.forEach(modelName => {
11
9
  console.log(` * ${chalk.green(modelName)}`);
12
10
  });
13
11
  console.log('');
@@ -1,13 +1,11 @@
1
1
 
2
2
  import chalk from 'chalk';
3
3
 
4
- import { NodeWrapper } from '../node_wrapper.js';
5
-
6
- export default () => {
4
+ export default ({ nodeWrapperNames }) => {
7
5
  console.log('');
8
6
  console.log('The following node wrappers are available:');
9
7
  console.log('');
10
- Object.keys(NodeWrapper.classes).sort().forEach(nodeWrapperName => {
8
+ nodeWrapperNames.forEach(nodeWrapperName => {
11
9
  console.log(` * ${chalk.green(nodeWrapperName)}`);
12
10
  });
13
11
  console.log('');
@@ -1,13 +1,11 @@
1
1
 
2
2
  import chalk from 'chalk';
3
3
 
4
- import { ServiceFactory } from '../service_factory.js';
5
-
6
- export default () => {
4
+ export default ({ serviceNames }) => {
7
5
  console.log('');
8
6
  console.log('The following services are available:');
9
7
  console.log('');
10
- Object.keys(ServiceFactory.classes).sort().forEach(serviceName => {
8
+ serviceNames.forEach(serviceName => {
11
9
  console.log(` * ${chalk.green(serviceName)}`);
12
10
  });
13
11
  console.log('');
@@ -1,13 +1,11 @@
1
1
 
2
2
  import chalk from 'chalk';
3
3
 
4
- import { View } from '../view.js';
5
-
6
- export default () => {
4
+ export default ({ viewNames }) => {
7
5
  console.log('');
8
6
  console.log('The following views are available:');
9
7
  console.log('');
10
- Object.keys(View.classes).sort().forEach(viewName => {
8
+ viewNames.forEach(viewName => {
11
9
  console.log(` * ${chalk.green(viewName)}`);
12
10
  });
13
11
  console.log('');
@@ -0,0 +1,12 @@
1
+
2
+ export default {
3
+ meta(){
4
+ this.schedule('*/5 * * * *');
5
+ },
6
+
7
+ minutesUntilExpiry: 30,
8
+
9
+ async run(){
10
+ await this.database.withoutMultiTenancy.sessions.lastAccessedAtLt(Date.now() - (1000 * 60 * this.minutesUntilExpiry)).delete();
11
+ }
12
+ };
@@ -1,10 +1,9 @@
1
1
 
2
2
  import http from 'http';
3
3
  import Busboy from 'busboy';
4
- import { default as qs} from 'qs';
5
- const { parse: parseQueryString } = qs;
4
+ import { Url } from '../url.js';
6
5
 
7
- export default async ({ fetch }) => {
6
+ export default async ({ fetch, bot }) => {
8
7
  const host = process.env.HOST || '127.0.0.1';
9
8
  const port = process.env.PORT || 3000;
10
9
 
@@ -27,20 +26,23 @@ export default async ({ fetch }) => {
27
26
  }).listen(port, host, () => {
28
27
  console.log(`Pinstripe running at http://${host}:${port}/`)
29
28
  });
29
+
30
+ bot.start();
31
+
32
+ return new Promise(() => {});
30
33
  };
31
34
 
32
35
  const extractParams = async (request) => {
33
36
  const { method, url, headers } = request;
34
- const matches = url.match(/^([^\?]+)\?(.*)$/);
35
- const path = matches ? matches[1] : url;
36
- const queryString = parseQueryString(matches ? matches[2] : "");
37
+ const _url = Url.fromString(url);
38
+ if(headers['x-host']) _url.host = headers['x-host'];
37
39
  const body = method.match(/^POST|PUT|PATCH$/) ? await parseBody(request) : {};
38
40
 
39
41
  return {
40
- ...queryString,
42
+ ..._url.params,
41
43
  ...body,
42
44
  _method: method,
43
- _path: path,
45
+ _url,
44
46
  _headers: headers
45
47
  };
46
48
  };
@@ -207,7 +207,7 @@ Adapter.register('mysql').include({
207
207
  const table = this._table;
208
208
  const database = table._database;
209
209
 
210
- await table.create();
210
+ if(!await table.exists()) await table.create();
211
211
 
212
212
  if(!await this.exists()){
213
213
  await database.run`
@@ -281,9 +281,20 @@ Adapter.register('mysql').include({
281
281
  return this;
282
282
  },
283
283
 
284
- _generateInsertSql(){
284
+ async _generateInsertSql(){
285
285
  this._fields['id'] = crypto.randomUUID();
286
286
  this._alteredFields['id'] = this._fields['id'];
287
+
288
+ const database = await this._database;
289
+ const { isMultiTenant } = database;
290
+ const isScopedToTenant = isMultiTenant && !this.constructor.name.match(/^(pinstripe[A-Z]|tenant$)/);
291
+ const tenant = isScopedToTenant && database._environment ? await database._environment.tenant : undefined;
292
+ if(tenant){
293
+ this._fields['tenantId'] = tenant.id;
294
+ this._alteredFields['tenantId'] = tenant.id;
295
+ } else if(isScopedToTenant) {
296
+ return this._database.renderSql`select NULL`;
297
+ }
287
298
 
288
299
  return this._database.renderSql`
289
300
  insert into ${this._adapter.escapeIdentifier(Inflector.pluralize(this.constructor.name))}(
@@ -352,7 +363,7 @@ Adapter.register('mysql').include({
352
363
  },
353
364
 
354
365
  async first(options = {}){
355
- return (await this.all({ ...options, limit: Sql.fromString('0, 1') })).pop();
366
+ return (await this.all({ ...options, limit: Sql.fromString('1'), offset: Sql.fromString('0') })).pop();
356
367
  },
357
368
 
358
369
  async count(options = {}){
@@ -486,12 +497,32 @@ Adapter.register('mysql').include({
486
497
  out.push(this.renderSql` from ${joinRoot._fromSql}`);
487
498
  }
488
499
 
500
+ const { isMultiTenant } = this._database;
501
+ const isScopedToTenant = isMultiTenant && !this.constructor.name.match(/^(pinstripe[A-Z]|tenants$)/);
502
+ const tenant = isScopedToTenant ? await this._database._environment.tenant : undefined;
503
+
489
504
  if (options.hasOwnProperty('where')){
490
505
  if(options.where){
491
506
  out.push(this.renderSql` where ${options.where}`);
507
+ if(tenant){
508
+ out.push(this.renderSql` and ${this.tenantId} = uuid_to_bin(${tenant.id})`);
509
+ } else if(isScopedToTenant){
510
+ out.push(this.renderSql` and 1 = 2`);
511
+ }
512
+ } else if(isScopedToTenant){
513
+ out.push(this.renderSql` where 1 = 2`);
492
514
  }
493
515
  } else if(joinRoot._whereSql.length) {
494
516
  out.push(this.renderSql` where ${joinRoot._whereSql}`);
517
+ if(tenant){
518
+ out.push(this.renderSql` and ${this.tenantId} = uuid_to_bin(${tenant.id})`);
519
+ } else if(isScopedToTenant){
520
+ out.push(this.renderSql` and 1 = 2`);
521
+ }
522
+ } else if(tenant){
523
+ out.push(this.renderSql` where ${this.tenantId} = uuid_to_bin(${tenant.id})`);
524
+ } else if(isScopedToTenant){
525
+ out.push(this.renderSql` where 1 = 2`);
495
526
  }
496
527
 
497
528
  if(options.hasOwnProperty('orderBy')){
@@ -506,9 +537,18 @@ Adapter.register('mysql').include({
506
537
  if(options.limit){
507
538
  out.push(this.renderSql` limit ${options.limit}`);
508
539
  }
509
- } else if(joinRoot._limit) {
510
- const { page, pageSize } = joinRoot._limit;
511
- out.push(this.renderSql` limit ${(page - 1) * pageSize}, ${pageSize}`);
540
+ } else if(joinRoot._pagination) {
541
+ const { pageSize } = joinRoot._pagination;
542
+ out.push(this.renderSql` limit ${pageSize}`);
543
+ }
544
+
545
+ if(options.hasOwnProperty('offset')){
546
+ if(options.offset){
547
+ out.push(this.renderSql` offset ${options.offset}`);
548
+ }
549
+ } else if(joinRoot._pagination || joinRoot._skipCount) {
550
+ const { page = 1, pageSize = 10 } = joinRoot._pagination || {};
551
+ out.push(this.renderSql` offset ${((page - 1) * pageSize) + joinRoot._skipCount}`);
512
552
  }
513
553
 
514
554
  return this.renderSql`${out}`;
@@ -559,10 +599,16 @@ Adapter.register('mysql').include({
559
599
  out.push(this.renderSql` order by ${this._orderBySql}`);
560
600
  }
561
601
 
562
- if(this._limit) {
563
- const { page, pageSize } = this._limit;
564
- out.push(this.renderSql` limit ${(page - 1) * pageSize}, ${pageSize}`);
602
+ if(this._pagination) {
603
+ const { pageSize } = this._pagination;
604
+ out.push(this.renderSql` limit ${pageSize}`);
565
605
  }
606
+
607
+ if(this._pagination || this._skipCount) {
608
+ const { page = 1, pageSize = 10 } = this._pagination || {};
609
+ out.push(this.renderSql` offset ${((page - 1) * pageSize) + this._skipCount}`);
610
+ }
611
+
566
612
  return this._database.renderSql`${out}`;
567
613
  }
568
614
  }
@@ -582,8 +628,6 @@ const TYPE_TO_COLUMN_TYPE_MAP = {
582
628
  integer: "int(11)",
583
629
  string: "varchar(255)",
584
630
  text: "longtext",
585
- time: "time",
586
- timestamp: "datetime"
587
631
  };
588
632
 
589
633
  const COLUMN_TYPE_TO_TYPE_MAP = (() => {
@@ -1,7 +1,7 @@
1
1
 
2
2
  import sqlite from 'sqlite3';
3
3
  import * as crypto from 'crypto';
4
- import { unlink } from 'fs';
4
+ import { existsSync, unlink } from 'fs';
5
5
  import { promisify } from 'util';
6
6
 
7
7
  import { Adapter } from '../adapter.js';
@@ -14,14 +14,15 @@ import { Table } from '../table.js';
14
14
  import { Row } from '../row.js';
15
15
 
16
16
  let schemaCache = {};
17
+ let connection;
17
18
 
18
19
  Adapter.register('sqlite').include({
19
20
  get connection(){
20
- if(!this._connection){
21
+ if(!connection){
21
22
  const { filename } = this.config;
22
- this._connection = new sqlite.Database(filename);
23
+ connection = new sqlite.Database(filename);
23
24
  }
24
- return this._connection;
25
+ return connection;
25
26
  },
26
27
 
27
28
  escapeValue(value){
@@ -51,13 +52,15 @@ Adapter.register('sqlite').include({
51
52
  },
52
53
 
53
54
  async drop() {
54
- await new Promise((resolve, reject) => {
55
- this._adapter.connection.close(error => error ? reject(error) : resolve());
56
- });
57
- delete this._adapter._connection;
55
+ if(connection){
56
+ await new Promise((resolve, reject) => {
57
+ connection.close(error => error ? reject(error) : resolve());
58
+ });
59
+ connection = undefined;
60
+ }
58
61
  schemaCache = {};
59
62
  this._sessionCache = {};
60
- await promisify(unlink)(this._adapter.config.filename);
63
+ if(existsSync(this._adapter.config.filename)) await promisify(unlink)(this._adapter.config.filename);
61
64
  },
62
65
 
63
66
  async tables(){
@@ -80,9 +83,7 @@ Adapter.register('sqlite').include({
80
83
  },
81
84
 
82
85
  async destroy() {
83
- await new Promise((resolve, reject) => {
84
- this._adapter.connection.close(error => error ? reject(error) : resolve());
85
- });
86
+ // do nothing
86
87
  },
87
88
 
88
89
  async _fetchRows(sql){
@@ -138,7 +139,19 @@ Adapter.register('sqlite').include({
138
139
  while(rows.length){
139
140
  const { _type, ...fields } = rows.shift();
140
141
  if(_type !== undefined){
141
- out.push(await Row.create(_type, this, fields));
142
+ const table = await this[Inflector.pluralize(_type)];
143
+ const mappedFields = {};
144
+ const names = Object.keys(fields);
145
+ while(names.length){
146
+ const name = names.shift();
147
+ const column = await table[name];
148
+ if(column && ['date', 'datetime'].includes(await column.type())){
149
+ mappedFields[name] = new Date(fields[name]);
150
+ } else {
151
+ mappedFields[name] = fields[name];
152
+ }
153
+ }
154
+ out.push(await Row.create(_type, this, mappedFields));
142
155
  } else {
143
156
  out.push(fields);
144
157
  }
@@ -169,7 +182,7 @@ Adapter.register('sqlite').include({
169
182
  const table = this._table;
170
183
  const database = table._database;
171
184
 
172
- await table.create();
185
+ if(!await table.exists()) await table.create();
173
186
 
174
187
  if(!await this.exists()){
175
188
  let defaultSql = options.default;
@@ -252,10 +265,21 @@ Adapter.register('sqlite').include({
252
265
  return this;
253
266
  },
254
267
 
255
- _generateInsertSql(){
268
+ async _generateInsertSql(){
256
269
  this._fields['id'] = crypto.randomUUID();
257
270
  this._alteredFields['id'] = this._fields['id'];
258
-
271
+
272
+ const database = await this._database;
273
+ const { isMultiTenant } = database;
274
+ const isScopedToTenant = isMultiTenant && !this.constructor.name.match(/^(pinstripe[A-Z]|tenant$)/);
275
+ const tenant = isScopedToTenant && database._environment ? await database._environment.tenant : undefined;
276
+ if(tenant){
277
+ this._fields['tenantId'] = tenant.id;
278
+ this._alteredFields['tenantId'] = tenant.id;
279
+ } else if(isScopedToTenant) {
280
+ return this._database.renderSql`select NULL`;
281
+ }
282
+
259
283
  return this._database.renderSql`
260
284
  insert into ${this._adapter.escapeIdentifier(Inflector.pluralize(this.constructor.name))}(
261
285
  ${Object.keys(this._alteredFields).map((key, i) =>
@@ -323,7 +347,7 @@ Adapter.register('sqlite').include({
323
347
  },
324
348
 
325
349
  async first(options = {}){
326
- return (await this.all({ ...options, limit: Sql.fromString('0, 1') })).pop();
350
+ return (await this.all({ ...options, limit: Sql.fromString('1'), offset: Sql.fromString('0') })).pop();
327
351
  },
328
352
 
329
353
  async count(options = {}){
@@ -345,6 +369,8 @@ Adapter.register('sqlite').include({
345
369
  type = 'primary_key';
346
370
  } else if(name == 'id'){
347
371
  type = 'alternate_key';
372
+ } else if(name.match(/.+Id$/)){
373
+ type = 'foreign_key';
348
374
  } else {
349
375
  type = COLUMN_TYPE_TO_TYPE_MAP[row.type] || 'string';
350
376
  }
@@ -444,23 +470,43 @@ Adapter.register('sqlite').include({
444
470
  }
445
471
  out.push(this.renderSql`${Inflector.singularize(this.constructor.name)} as \`_type\``);
446
472
  }
447
-
448
- const joinRoot = this._joinRoot;
449
473
 
474
+ const joinRoot = this._joinRoot;
450
475
  if(options.hasOwnProperty('from')){
451
476
  if(options.from){
452
477
  out.push(this.renderSql` from ${options.from}`);
453
478
  }
454
479
  } else {
455
480
  out.push(this.renderSql` from ${joinRoot._fromSql}`);
481
+
456
482
  }
483
+
484
+ const { isMultiTenant } = this._database;
485
+ const isScopedToTenant = isMultiTenant && !this.constructor.name.match(/^(pinstripe[A-Z]|tenants$)/);
486
+ const tenant = isScopedToTenant ? await this._database._environment.tenant : undefined;
457
487
 
458
488
  if (options.hasOwnProperty('where')){
459
489
  if(options.where){
460
490
  out.push(this.renderSql` where ${options.where}`);
491
+ if(tenant){
492
+ out.push(this.renderSql` and ${this.tenantId} = ${tenant.id}`);
493
+ } else if(isScopedToTenant){
494
+ out.push(this.renderSql` where 1 = 2`);
495
+ }
496
+ } else if(isScopedToTenant){
497
+ out.push(this.renderSql` where 1 = 2`);
461
498
  }
462
499
  } else if(joinRoot._whereSql.length) {
463
500
  out.push(this.renderSql` where ${joinRoot._whereSql}`);
501
+ if(tenant){
502
+ out.push(this.renderSql` and ${this.tenantId} = ${tenant.id}`);
503
+ } else if(isScopedToTenant){
504
+ out.push(this.renderSql` and 1 = 2`);
505
+ }
506
+ } else if(tenant){
507
+ out.push(this.renderSql` where ${this.tenantId} = ${tenant.id}`);
508
+ } else if(isScopedToTenant){
509
+ out.push(this.renderSql` where 1 = 2`);
464
510
  }
465
511
 
466
512
  if(options.hasOwnProperty('orderBy')){
@@ -475,9 +521,18 @@ Adapter.register('sqlite').include({
475
521
  if(options.limit){
476
522
  out.push(this.renderSql` limit ${options.limit}`);
477
523
  }
478
- } else if(joinRoot._limit) {
479
- const { page, pageSize } = joinRoot._limit;
480
- out.push(this.renderSql` limit ${(page - 1) * pageSize}, ${pageSize}`);
524
+ } else if(joinRoot._pagination) {
525
+ const { pageSize } = joinRoot._pagination;
526
+ out.push(this.renderSql` limit ${pageSize}`);
527
+ }
528
+
529
+ if(options.hasOwnProperty('offset')){
530
+ if(options.offset){
531
+ out.push(this.renderSql` offset ${options.offset}`);
532
+ }
533
+ } else if(joinRoot._pagination || joinRoot._skipCount) {
534
+ const { page = 1, pageSize = 10 } = joinRoot._pagination || {};
535
+ out.push(this.renderSql` offset ${((page - 1) * pageSize) + joinRoot._skipCount}`);
481
536
  }
482
537
 
483
538
  return this.renderSql`${out}`;
@@ -527,11 +582,17 @@ Adapter.register('sqlite').include({
527
582
  if(this._orderBySql.length){
528
583
  out.push(this.renderSql` order by ${this._orderBySql}`);
529
584
  }
530
-
531
- if(this._limit) {
532
- const { page, pageSize } = this._limit;
533
- out.push(this.renderSql` limit ${(page - 1) * pageSize}, ${pageSize}`);
585
+
586
+ if(this._pagination) {
587
+ const { pageSize } = this._pagination;
588
+ out.push(this.renderSql` limit ${pageSize}`);
534
589
  }
590
+
591
+ if(this._pagination || this._skipCount) {
592
+ const { page = 1, pageSize = 10 } = this._pagination || {};
593
+ out.push(this.renderSql` offset ${((page - 1) * pageSize) + this._skipCount}`);
594
+ }
595
+
535
596
  return this._database.renderSql`${out}`;
536
597
  }
537
598
  }
@@ -551,8 +612,6 @@ const TYPE_TO_COLUMN_TYPE_MAP = {
551
612
  integer: "integer",
552
613
  string: "varchar",
553
614
  text: "text",
554
- time: "time",
555
- timestamp: "datetime"
556
615
  };
557
616
 
558
617
  const COLUMN_TYPE_TO_TYPE_MAP = (() => {
@@ -7,8 +7,6 @@ export const ALLOWED_TABLE_ADAPTER_COLUMN_TYPES = [
7
7
  'float',
8
8
  'integer',
9
9
  'string',
10
- 'time',
11
- 'timestamp'
12
10
  ];
13
11
 
14
12
  export const TYPE_TO_DEFAULT_VALUE_MAP = {
@@ -32,8 +30,6 @@ export const COLUMN_TYPE_TO_FORM_FIELD_TYPE_MAP = {
32
30
  integer: "number",
33
31
  string: "text",
34
32
  text: "textarea",
35
- time: "datetime-local",
36
- timestamp: "datetime-local"
37
33
  };
38
34
 
39
35
  export const RESERVED_WORDS = [