remult 0.24.1 → 0.24.2-exp.1

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 (44) hide show
  1. package/README.md +4 -4
  2. package/esm/index.js +4 -3
  3. package/esm/remult-sql-js.js +113 -0
  4. package/esm/src/data-providers/array-entity-data-provider.js +1 -1
  5. package/esm/src/data-providers/json-data-provider.js +1 -1
  6. package/esm/src/data-providers/json-entity-opfs-storage.js +22 -0
  7. package/esm/src/data-providers/sql-database.js +10 -6
  8. package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +4 -5
  9. package/esm/src/remult-proxy.js +4 -0
  10. package/esm/src/remult3/Fields.js +22 -0
  11. package/esm/src/remult3/RepositoryImplementation.js +109 -60
  12. package/esm/src/remult3/entity.js +2 -2
  13. package/esm/src/remult3/getEntityRef.js +5 -5
  14. package/esm/src/remult3/relationInfoMember.js +1 -1
  15. package/esm/src/remult3/remult3.js +5 -7
  16. package/esm/src/server-action-info.js +1 -1
  17. package/esm/src/server-action.js +1 -1
  18. package/esm/src/validators.js +117 -28
  19. package/index.d.ts +5 -3
  20. package/index.js +10 -3
  21. package/package.json +6 -1
  22. package/remult-sql-js.d.ts +18 -0
  23. package/remult-sql-js.js +211 -0
  24. package/src/column-interfaces.d.ts +12 -6
  25. package/src/data-providers/array-entity-data-provider.js +9 -10
  26. package/src/data-providers/json-data-provider.js +1 -1
  27. package/src/data-providers/json-entity-opfs-storage.d.ts +5 -0
  28. package/src/data-providers/json-entity-opfs-storage.js +60 -0
  29. package/src/data-providers/sql-database.js +14 -6
  30. package/src/filter/filter-consumer-bridge-to-sql-request.js +4 -5
  31. package/src/remult-proxy.js +11 -0
  32. package/src/remult3/Fields.js +22 -0
  33. package/src/remult3/RepositoryImplementation.d.ts +6 -6
  34. package/src/remult3/RepositoryImplementation.js +136 -78
  35. package/src/remult3/entity.js +2 -2
  36. package/src/remult3/getEntityRef.js +5 -5
  37. package/src/remult3/relationInfoMember.js +1 -1
  38. package/src/remult3/remult3.d.ts +33 -0
  39. package/src/remult3/remult3.js +5 -7
  40. package/src/server-action-info.js +1 -1
  41. package/src/server-action.js +1 -1
  42. package/src/sql-command.d.ts +1 -0
  43. package/src/validators.d.ts +41 -9
  44. package/src/validators.js +180 -54
package/README.md CHANGED
@@ -27,9 +27,9 @@
27
27
  <br/>
28
28
 
29
29
  <div align="center">
30
- <a href="#getting-started">Getting Started</a> |
31
- <a href="#documentation">Documentation</a> |
32
- <a href="#example-apps">Example Apps</a>
30
+ <a href="#tutorials">Tutorials</a> |
31
+ <a href="https://remult.dev/docs/quickstart">Quickstart</a> |
32
+ <a href="https://remult.dev/docs/example-apps">Example Apps</a>
33
33
  </div>
34
34
 
35
35
  <hr/>
@@ -268,7 +268,7 @@ numerous ways:
268
268
  [Backend Methods](https://remult.dev/docs/backendMethods.html)
269
269
  - Roll-your-own low-level endpoints (Express, Fastify, koa, others…)
270
270
 
271
- ## Getting started
271
+ ## Tutorials
272
272
 
273
273
  The best way to learn Remult is by following a tutorial of a simple Todo web app
274
274
  with a Node.js Express backend.
package/esm/index.js CHANGED
@@ -10,6 +10,7 @@ export { WebSqlDataProvider } from './src/data-providers/web-sql-data-provider.j
10
10
  export { SqlDatabase } from './src/data-providers/sql-database.js'; //V
11
11
  export { CustomSqlFilterBuilder, dbNamesOf, } from './src/filter/filter-consumer-bridge-to-sql-request.js';
12
12
  export { JsonDataProvider, } from './src/data-providers/json-data-provider.js'; //V
13
+ export { JsonEntityOpfsStorage } from './src/data-providers/json-entity-opfs-storage.js';
13
14
  //export * from './src/data-api'; //reconsider if to make internal
14
15
  export { Controller, BackendMethod, ProgressListener, } from './src/server-action.js';
15
16
  export { Allow, Remult, withRemult, isBackend, EventSource, } from './src/context.js';
@@ -17,7 +18,7 @@ export { Sort } from './src/sort.js';
17
18
  export { CompoundIdField } from './src/CompoundIdField.js';
18
19
  export { Filter, } from './src/filter/filter-interfaces.js';
19
20
  export { UrlBuilder } from './urlBuilder.js';
20
- export { Validators } from './src/validators.js';
21
+ export { Validators, valueValidator, createValidator, createValidatorWithArgs, createValueValidatorWithArgs, createValueValidator, } from './src/validators.js';
21
22
  export { ValueConverters } from './src/valueConverters.js';
22
23
  export { remult } from './src/remult-proxy.js';
23
24
  import { remult } from './src/remult-proxy.js';
@@ -36,6 +37,6 @@ export { SubscriptionChannel, } from './src/live-query/SubscriptionChannel.js';
36
37
  * await repo(Customer).insert()
37
38
  * ```
38
39
  */
39
- export function repo(entity) {
40
- return remult.repo(entity);
40
+ export function repo(entity, dataProvider) {
41
+ return remult.repo(entity, dataProvider);
41
42
  }
@@ -0,0 +1,113 @@
1
+ import { SqlDatabase } from './src/data-providers/sql-database.js';
2
+ import { dbNamesOf, isDbReadonly, } from './src/filter/filter-consumer-bridge-to-sql-request.js';
3
+ import { isAutoIncrement } from './src/remult3/RepositoryImplementation.js';
4
+ export class SqlJsDataProvider {
5
+ db;
6
+ constructor(db) {
7
+ this.db = db;
8
+ }
9
+ getLimitSqlSyntax(limit, offset) {
10
+ return ' limit ' + limit + ' offset ' + offset;
11
+ }
12
+ afterMutation;
13
+ createCommand() {
14
+ return new SqlJsCommand(this.db);
15
+ }
16
+ transaction(action) {
17
+ throw new Error('Method not implemented.');
18
+ }
19
+ async entityIsUsedForTheFirstTime(entity) {
20
+ await this.createTable(entity);
21
+ }
22
+ async ensureSchema(entities) {
23
+ for (const entity of entities) {
24
+ await this.createTable(entity);
25
+ }
26
+ }
27
+ async dropTable(entity) {
28
+ let e = await dbNamesOf(entity);
29
+ let sql = 'drop table if exists ' + e.$entityName;
30
+ if (SqlDatabase.LogToConsole)
31
+ console.info(sql);
32
+ await this.createCommand().execute(sql);
33
+ }
34
+ addColumnSqlSyntax(x, dbName) {
35
+ let result = dbName;
36
+ const nullNumber = x.allowNull ? '' : ' default 0 not null';
37
+ if (x.valueType == Date)
38
+ result += ' integer';
39
+ else if (x.valueType == Boolean)
40
+ result += ' integer ' + nullNumber;
41
+ else if (x.valueType == Number) {
42
+ if (!x.valueConverter.fieldTypeInDb)
43
+ result += ' real ' + nullNumber;
44
+ else
45
+ result += ' ' + x.valueConverter.fieldTypeInDb + ' ' + nullNumber;
46
+ }
47
+ else
48
+ result += ' text' + (x.allowNull ? ' ' : " default '' not null ");
49
+ return result;
50
+ }
51
+ async createTable(entity) {
52
+ let result = '';
53
+ let e = await dbNamesOf(entity);
54
+ for (const x of entity.fields) {
55
+ if (!isDbReadonly(x, e) || isAutoIncrement(x)) {
56
+ if (result.length != 0)
57
+ result += ',';
58
+ result += '\r\n ';
59
+ if (isAutoIncrement(x)) {
60
+ if (x.key != entity.idMetadata.field.key)
61
+ throw 'in web sql, autoincrement is only allowed for primary key';
62
+ result += e.$dbNameOf(x) + ' integer primary key autoincrement';
63
+ }
64
+ else {
65
+ result += this.addColumnSqlSyntax(x, e.$dbNameOf(x));
66
+ if (x.key == entity.idMetadata.field.key) {
67
+ result += ' primary key';
68
+ }
69
+ }
70
+ }
71
+ }
72
+ let sql = 'create table if not exists ' + e.$entityName + ' (' + result + '\r\n)';
73
+ if (SqlDatabase.LogToConsole)
74
+ console.log(sql);
75
+ await this.createCommand().execute(sql);
76
+ }
77
+ supportsJsonColumnType;
78
+ wrapIdentifier(name) {
79
+ return name;
80
+ }
81
+ }
82
+ class SqlJsCommand {
83
+ db;
84
+ values = {};
85
+ i = 0;
86
+ constructor(db) {
87
+ this.db = db;
88
+ }
89
+ async execute(sql) {
90
+ if (this.i == 0)
91
+ return new SqlJsSqlResult((await this.db).exec(sql));
92
+ return new SqlJsSqlResult((await this.db).exec(sql, this.values));
93
+ }
94
+ addParameterAndReturnSqlToken(val) {
95
+ if (val instanceof Date)
96
+ val = val.valueOf();
97
+ const key = ':' + ++this.i;
98
+ this.values[key] = val;
99
+ return key;
100
+ }
101
+ }
102
+ class SqlJsSqlResult {
103
+ result;
104
+ constructor(result) {
105
+ this.result = result;
106
+ this.rows =
107
+ result[0]?.values.map((row) => row.reduce((prev, curr, i) => ({ ...prev, [result[0].columns[i]]: curr }), {})) ?? [];
108
+ }
109
+ rows;
110
+ getColumnKeyInResultForIndexInSelect(index) {
111
+ return this.result[0]?.columns[index];
112
+ }
113
+ }
@@ -157,7 +157,7 @@ export class ArrayEntityDataProvider {
157
157
  let j = this.translateToJson(data, names);
158
158
  let idf = this.entity.idMetadata.field;
159
159
  if (!(idf instanceof CompoundIdField)) {
160
- if (idf.options.valueConverter?.fieldTypeInDb === 'autoincrement') {
160
+ if (idf.valueConverter.fieldTypeInDb === 'autoincrement') {
161
161
  j[idf.key] = 1;
162
162
  for (const row of this.rows) {
163
163
  if (row[idf.key] >= j[idf.key])
@@ -20,7 +20,7 @@ class JsonEntityDataProvider {
20
20
  }
21
21
  async loadEntityData(what) {
22
22
  let data = [];
23
- let dbName = await this.entity.options.dbName;
23
+ let dbName = await this.entity.dbName;
24
24
  let s = await this.helper.getItem(dbName);
25
25
  if (s)
26
26
  data = JSON.parse(s);
@@ -0,0 +1,22 @@
1
+ export class JsonEntityOpfsStorage {
2
+ //@internal
3
+ opfsRoot;
4
+ async getItem(entityDbName) {
5
+ if (!this.opfsRoot) {
6
+ this.opfsRoot = await navigator.storage.getDirectory();
7
+ }
8
+ const opfsFile = await this.opfsRoot.getFileHandle(entityDbName + '.json', {
9
+ create: true,
10
+ });
11
+ const readable = await opfsFile.getFile();
12
+ return await readable.text();
13
+ }
14
+ async setItem(entityDbName, json) {
15
+ const opfsFile = await this.opfsRoot.getFileHandle(entityDbName + '.json', {
16
+ create: true,
17
+ });
18
+ const writable = await opfsFile.createWritable();
19
+ await writable.write(json);
20
+ await writable.close();
21
+ }
22
+ }
@@ -324,6 +324,7 @@ class ActualSQLServerDataProvider {
324
324
  let { colKeys, select } = this.buildSelect(e);
325
325
  statement += ' returning ' + select;
326
326
  return r.execute(statement).then((sqlResult) => {
327
+ this.sql._getSourceSql().afterMutation?.();
327
328
  return this.buildResultRow(colKeys, sqlResult.rows[0], sqlResult);
328
329
  });
329
330
  }
@@ -334,7 +335,9 @@ class ActualSQLServerDataProvider {
334
335
  Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
335
336
  let statement = 'delete from ' + e.$entityName;
336
337
  statement += await f.resolveWhere();
337
- return r.execute(statement).then(() => { });
338
+ return r.execute(statement).then(() => {
339
+ this.sql._getSourceSql().afterMutation?.();
340
+ });
338
341
  }
339
342
  async insert(data) {
340
343
  let e = await this.init();
@@ -362,9 +365,10 @@ class ActualSQLServerDataProvider {
362
365
  let statement = `insert into ${e.$entityName} (${cols}) values (${vals})`;
363
366
  let { colKeys, select } = this.buildSelect(e);
364
367
  statement += ' returning ' + select;
365
- return await r
366
- .execute(statement)
367
- .then((sql) => this.buildResultRow(colKeys, sql.rows[0], sql));
368
+ return await r.execute(statement).then((sql) => {
369
+ this.sql._getSourceSql().afterMutation?.();
370
+ return this.buildResultRow(colKeys, sql.rows[0], sql);
371
+ });
368
372
  }
369
373
  }
370
374
  class myDummySQLCommand {
@@ -393,9 +397,9 @@ async function bulkInsert(array, db) {
393
397
  // do whatever
394
398
  const c = db.createCommand();
395
399
  let sql = 'insert into ' +
396
- (await items[0]._.metadata.options.dbName) +
400
+ (await items[0]._.metadata.dbName) +
397
401
  ' (' +
398
- (await Promise.all(items[0]._.metadata.fields.toArray().map((f) => f.options.dbName))).join(',') +
402
+ (await Promise.all(items[0]._.metadata.fields.toArray().map((f) => f.dbName))).join(',') +
399
403
  ') values ';
400
404
  sql += items
401
405
  .map((row) => '(' +
@@ -152,8 +152,7 @@ export class CustomSqlFilterBuilder {
152
152
  export function isDbReadonly(field, dbNames) {
153
153
  return (field.dbReadOnly ||
154
154
  field.isServerExpression ||
155
- (field.options.sqlExpression &&
156
- field.options.dbName != dbNames.$dbNameOf(field)));
155
+ (field.options.sqlExpression && field.dbName != dbNames.$dbNameOf(field)));
157
156
  }
158
157
  export async function dbNamesOf(repo, wrapIdentifier = (x) => x) {
159
158
  var meta = getEntityMetadata(repo);
@@ -182,7 +181,7 @@ export async function entityDbName(metadata, wrapIdentifier = (x) => x) {
182
181
  return await metadata.options.sqlExpression(metadata);
183
182
  }
184
183
  }
185
- return wrapIdentifier(metadata.options.dbName);
184
+ return wrapIdentifier(metadata.dbName);
186
185
  }
187
186
  export async function fieldDbName(f, meta, wrapIdentifier = (x) => x) {
188
187
  try {
@@ -194,7 +193,7 @@ export async function fieldDbName(f, meta, wrapIdentifier = (x) => x) {
194
193
  else
195
194
  result = f.options.sqlExpression;
196
195
  if (!result)
197
- return f.options.dbName;
196
+ return f.dbName;
198
197
  return result;
199
198
  }
200
199
  const rel = getRelationInfo(f.options);
@@ -205,7 +204,7 @@ export async function fieldDbName(f, meta, wrapIdentifier = (x) => x) {
205
204
  if (fInfo)
206
205
  return fieldDbName(fInfo, meta, wrapIdentifier);
207
206
  }
208
- return wrapIdentifier(f.options.dbName);
207
+ return wrapIdentifier(f.dbName);
209
208
  }
210
209
  finally {
211
210
  }
@@ -120,6 +120,10 @@ export class RemultProxy {
120
120
  .remultFactory()
121
121
  .repo(...args)
122
122
  .findFirst(...args2),
123
+ findOne: (...args2) => self
124
+ .remultFactory()
125
+ .repo(...args)
126
+ .findOne(...args2),
123
127
  findId: (a, b) => self
124
128
  .remultFactory()
125
129
  .repo(...args)
@@ -3,6 +3,7 @@ import { v4 as uuid } from 'uuid';
3
3
  import { ValueConverters } from '../valueConverters.js';
4
4
  import { buildOptions, columnsOfType } from './RepositoryImplementation.js';
5
5
  import { relationInfoMember } from './relationInfoMember.js';
6
+ import { Validators } from '../validators.js';
6
7
  export class Fields {
7
8
  /**
8
9
  * Stored as a JSON.stringify - to store as json use Fields.json
@@ -198,6 +199,12 @@ export function Field(valueType, ...options) {
198
199
  const key = typeof context === 'string' ? context : context.name.toString();
199
200
  let factory = (remult) => {
200
201
  let r = buildOptions(options, remult);
202
+ if (r.required) {
203
+ r.validate = addValidator(r.validate, Validators.required);
204
+ }
205
+ if (r.maxLength) {
206
+ r.validate = addValidator(r.validate, Validators.maxLength(r.maxLength));
207
+ }
201
208
  if (!r.valueType && valueType) {
202
209
  r.valueType = valueType();
203
210
  }
@@ -241,3 +248,18 @@ export function checkTarget(target) {
241
248
  if (!target)
242
249
  throw new Error("Set the 'experimentalDecorators:true' option in your 'tsconfig' or 'jsconfig' (target undefined)");
243
250
  }
251
+ function addValidator(validators, newValidator, atStart = false) {
252
+ if (!newValidator)
253
+ return;
254
+ const newValidators = Array.isArray(newValidator)
255
+ ? newValidator
256
+ : [newValidator];
257
+ const validatorsArray = Array.isArray(validators)
258
+ ? validators
259
+ : validators
260
+ ? [validators]
261
+ : [];
262
+ return atStart
263
+ ? [...newValidators, ...validatorsArray]
264
+ : [...validatorsArray, ...newValidators];
265
+ }