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.
- package/README.md +4 -4
- package/esm/index.js +4 -3
- package/esm/remult-sql-js.js +113 -0
- package/esm/src/data-providers/array-entity-data-provider.js +1 -1
- package/esm/src/data-providers/json-data-provider.js +1 -1
- package/esm/src/data-providers/json-entity-opfs-storage.js +22 -0
- package/esm/src/data-providers/sql-database.js +10 -6
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +4 -5
- package/esm/src/remult-proxy.js +4 -0
- package/esm/src/remult3/Fields.js +22 -0
- package/esm/src/remult3/RepositoryImplementation.js +109 -60
- package/esm/src/remult3/entity.js +2 -2
- package/esm/src/remult3/getEntityRef.js +5 -5
- package/esm/src/remult3/relationInfoMember.js +1 -1
- package/esm/src/remult3/remult3.js +5 -7
- package/esm/src/server-action-info.js +1 -1
- package/esm/src/server-action.js +1 -1
- package/esm/src/validators.js +117 -28
- package/index.d.ts +5 -3
- package/index.js +10 -3
- package/package.json +6 -1
- package/remult-sql-js.d.ts +18 -0
- package/remult-sql-js.js +211 -0
- package/src/column-interfaces.d.ts +12 -6
- package/src/data-providers/array-entity-data-provider.js +9 -10
- package/src/data-providers/json-data-provider.js +1 -1
- package/src/data-providers/json-entity-opfs-storage.d.ts +5 -0
- package/src/data-providers/json-entity-opfs-storage.js +60 -0
- package/src/data-providers/sql-database.js +14 -6
- package/src/filter/filter-consumer-bridge-to-sql-request.js +4 -5
- package/src/remult-proxy.js +11 -0
- package/src/remult3/Fields.js +22 -0
- package/src/remult3/RepositoryImplementation.d.ts +6 -6
- package/src/remult3/RepositoryImplementation.js +136 -78
- package/src/remult3/entity.js +2 -2
- package/src/remult3/getEntityRef.js +5 -5
- package/src/remult3/relationInfoMember.js +1 -1
- package/src/remult3/remult3.d.ts +33 -0
- package/src/remult3/remult3.js +5 -7
- package/src/server-action-info.js +1 -1
- package/src/server-action.js +1 -1
- package/src/sql-command.d.ts +1 -0
- package/src/validators.d.ts +41 -9
- 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="#
|
|
31
|
-
<a href="
|
|
32
|
-
<a href="
|
|
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
|
-
##
|
|
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.
|
|
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])
|
|
@@ -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
|
-
.
|
|
367
|
-
|
|
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.
|
|
400
|
+
(await items[0]._.metadata.dbName) +
|
|
397
401
|
' (' +
|
|
398
|
-
(await Promise.all(items[0]._.metadata.fields.toArray().map((f) => f.
|
|
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.
|
|
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.
|
|
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.
|
|
207
|
+
return wrapIdentifier(f.dbName);
|
|
209
208
|
}
|
|
210
209
|
finally {
|
|
211
210
|
}
|
package/esm/src/remult-proxy.js
CHANGED
|
@@ -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
|
+
}
|