@promakeai/orm 1.0.6 → 1.3.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 +155 -185
- package/dist/adapters/RestAdapter.d.ts +94 -0
- package/dist/index.d.ts +12 -13
- package/dist/index.js +374 -192
- package/dist/schema/index.d.ts +1 -3
- package/dist/schema/schemaHelpers.d.ts +5 -1
- package/dist/schema/validator.d.ts +2 -0
- package/dist/types.d.ts +16 -15
- package/package.json +1 -1
- package/src/adapters/RestAdapter.ts +483 -0
- package/src/index.ts +23 -26
- package/src/schema/index.ts +1 -4
- package/src/schema/schemaHelpers.ts +8 -1
- package/src/schema/validator.ts +33 -1
- package/src/types.ts +21 -17
- package/src/utils/jsonConverter.ts +124 -117
- package/src/utils/translationQuery.ts +62 -62
- package/src/schema/defineSchema.ts +0 -164
- package/src/schema/fieldBuilder.ts +0 -293
package/dist/index.js
CHANGED
|
@@ -344,8 +344,13 @@ function getRefTargetFull(field) {
|
|
|
344
344
|
}
|
|
345
345
|
return { table: field.ref.table, field: field.ref.field || "id" };
|
|
346
346
|
}
|
|
347
|
+
function getTablePermissions(table) {
|
|
348
|
+
return table.permissions;
|
|
349
|
+
}
|
|
347
350
|
|
|
348
351
|
// src/schema/validator.ts
|
|
352
|
+
var VALID_ROLES = ["anon", "user", "admin"];
|
|
353
|
+
var VALID_ACTIONS = ["create", "read", "update", "delete"];
|
|
349
354
|
var ValidationErrorCode = {
|
|
350
355
|
MISSING_PRIMARY_KEY: "MISSING_PRIMARY_KEY",
|
|
351
356
|
INVALID_REFERENCE: "INVALID_REFERENCE",
|
|
@@ -353,7 +358,9 @@ var ValidationErrorCode = {
|
|
|
353
358
|
NO_LANGUAGES: "NO_LANGUAGES",
|
|
354
359
|
DUPLICATE_TABLE: "DUPLICATE_TABLE",
|
|
355
360
|
RESERVED_FIELD_NAME: "RESERVED_FIELD_NAME",
|
|
356
|
-
SELF_REFERENCE_ON_REQUIRED: "SELF_REFERENCE_ON_REQUIRED"
|
|
361
|
+
SELF_REFERENCE_ON_REQUIRED: "SELF_REFERENCE_ON_REQUIRED",
|
|
362
|
+
INVALID_PERMISSION_ROLE: "INVALID_PERMISSION_ROLE",
|
|
363
|
+
INVALID_PERMISSION_ACTION: "INVALID_PERMISSION_ACTION"
|
|
357
364
|
};
|
|
358
365
|
var RESERVED_FIELDS = ["language_code"];
|
|
359
366
|
function validateSchema(schema) {
|
|
@@ -412,6 +419,30 @@ function validateSchema(schema) {
|
|
|
412
419
|
});
|
|
413
420
|
}
|
|
414
421
|
}
|
|
422
|
+
if (table.permissions) {
|
|
423
|
+
for (const [role, actions] of Object.entries(table.permissions)) {
|
|
424
|
+
if (!VALID_ROLES.includes(role)) {
|
|
425
|
+
errors.push({
|
|
426
|
+
table: tableName,
|
|
427
|
+
field: "$permissions",
|
|
428
|
+
message: `Invalid permission role: "${role}". Valid roles: ${VALID_ROLES.join(", ")}`,
|
|
429
|
+
code: ValidationErrorCode.INVALID_PERMISSION_ROLE
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
if (Array.isArray(actions)) {
|
|
433
|
+
for (const action of actions) {
|
|
434
|
+
if (!VALID_ACTIONS.includes(action)) {
|
|
435
|
+
errors.push({
|
|
436
|
+
table: tableName,
|
|
437
|
+
field: "$permissions",
|
|
438
|
+
message: `Invalid permission action: "${action}". Valid actions: ${VALID_ACTIONS.join(", ")}`,
|
|
439
|
+
code: ValidationErrorCode.INVALID_PERMISSION_ACTION
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
415
446
|
}
|
|
416
447
|
return errors;
|
|
417
448
|
}
|
|
@@ -458,191 +489,6 @@ function validateTable(tableName, table, allTableNames) {
|
|
|
458
489
|
}
|
|
459
490
|
return errors;
|
|
460
491
|
}
|
|
461
|
-
|
|
462
|
-
// src/schema/fieldBuilder.ts
|
|
463
|
-
class FieldBuilder {
|
|
464
|
-
definition;
|
|
465
|
-
constructor(type) {
|
|
466
|
-
this.definition = {
|
|
467
|
-
type,
|
|
468
|
-
nullable: true,
|
|
469
|
-
unique: false,
|
|
470
|
-
primary: false,
|
|
471
|
-
translatable: false
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
required() {
|
|
475
|
-
this.definition.nullable = false;
|
|
476
|
-
return this;
|
|
477
|
-
}
|
|
478
|
-
nullable() {
|
|
479
|
-
this.definition.nullable = true;
|
|
480
|
-
return this;
|
|
481
|
-
}
|
|
482
|
-
unique() {
|
|
483
|
-
this.definition.unique = true;
|
|
484
|
-
return this;
|
|
485
|
-
}
|
|
486
|
-
default(value) {
|
|
487
|
-
this.definition.default = value;
|
|
488
|
-
return this;
|
|
489
|
-
}
|
|
490
|
-
translatable() {
|
|
491
|
-
this.definition.translatable = true;
|
|
492
|
-
return this;
|
|
493
|
-
}
|
|
494
|
-
ref(tableOrOptions) {
|
|
495
|
-
this.definition.ref = tableOrOptions;
|
|
496
|
-
return this;
|
|
497
|
-
}
|
|
498
|
-
trim() {
|
|
499
|
-
this.definition.trim = true;
|
|
500
|
-
return this;
|
|
501
|
-
}
|
|
502
|
-
lowercase() {
|
|
503
|
-
this.definition.lowercase = true;
|
|
504
|
-
return this;
|
|
505
|
-
}
|
|
506
|
-
uppercase() {
|
|
507
|
-
this.definition.uppercase = true;
|
|
508
|
-
return this;
|
|
509
|
-
}
|
|
510
|
-
minLength(value) {
|
|
511
|
-
this.definition.minLength = value;
|
|
512
|
-
return this;
|
|
513
|
-
}
|
|
514
|
-
maxLength(value) {
|
|
515
|
-
this.definition.maxLength = value;
|
|
516
|
-
return this;
|
|
517
|
-
}
|
|
518
|
-
min(value) {
|
|
519
|
-
this.definition.min = value;
|
|
520
|
-
return this;
|
|
521
|
-
}
|
|
522
|
-
max(value) {
|
|
523
|
-
this.definition.max = value;
|
|
524
|
-
return this;
|
|
525
|
-
}
|
|
526
|
-
enum(values) {
|
|
527
|
-
this.definition.enum = values;
|
|
528
|
-
return this;
|
|
529
|
-
}
|
|
530
|
-
match(pattern) {
|
|
531
|
-
this.definition.match = pattern;
|
|
532
|
-
return this;
|
|
533
|
-
}
|
|
534
|
-
build() {
|
|
535
|
-
return { ...this.definition };
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
var f = {
|
|
539
|
-
id: () => {
|
|
540
|
-
const builder = new FieldBuilder("id");
|
|
541
|
-
builder["definition"].primary = true;
|
|
542
|
-
builder["definition"].nullable = false;
|
|
543
|
-
return builder;
|
|
544
|
-
},
|
|
545
|
-
string: () => new FieldBuilder("string"),
|
|
546
|
-
text: () => new FieldBuilder("text"),
|
|
547
|
-
int: () => new FieldBuilder("int"),
|
|
548
|
-
decimal: () => new FieldBuilder("decimal"),
|
|
549
|
-
bool: () => new FieldBuilder("bool"),
|
|
550
|
-
timestamp: () => new FieldBuilder("timestamp"),
|
|
551
|
-
json: () => new FieldBuilder("json"),
|
|
552
|
-
stringArray: () => new FieldBuilder("string[]"),
|
|
553
|
-
numberArray: () => new FieldBuilder("number[]"),
|
|
554
|
-
boolArray: () => new FieldBuilder("boolean[]"),
|
|
555
|
-
object: (properties) => {
|
|
556
|
-
const builder = new FieldBuilder("object");
|
|
557
|
-
builder["definition"].properties = properties;
|
|
558
|
-
return builder;
|
|
559
|
-
},
|
|
560
|
-
objectArray: (properties) => {
|
|
561
|
-
const builder = new FieldBuilder("object[]");
|
|
562
|
-
builder["definition"].properties = properties;
|
|
563
|
-
return builder;
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
// src/schema/defineSchema.ts
|
|
568
|
-
function defineSchema(input) {
|
|
569
|
-
const languages = normalizeLanguages(input.languages);
|
|
570
|
-
const tables = {};
|
|
571
|
-
for (const [tableName, fields] of Object.entries(input.tables)) {
|
|
572
|
-
tables[tableName] = {
|
|
573
|
-
name: tableName,
|
|
574
|
-
fields: Object.fromEntries(Object.entries(fields).map(([fieldName, builder]) => [
|
|
575
|
-
fieldName,
|
|
576
|
-
builder.build()
|
|
577
|
-
]))
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
const schema = {
|
|
581
|
-
name: input.name,
|
|
582
|
-
languages,
|
|
583
|
-
tables
|
|
584
|
-
};
|
|
585
|
-
assertValidSchema(schema);
|
|
586
|
-
return schema;
|
|
587
|
-
}
|
|
588
|
-
function normalizeLanguages(input) {
|
|
589
|
-
if (Array.isArray(input)) {
|
|
590
|
-
if (input.length === 0) {
|
|
591
|
-
throw new Error("At least one language must be defined");
|
|
592
|
-
}
|
|
593
|
-
return {
|
|
594
|
-
default: input[0],
|
|
595
|
-
supported: input
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
return input;
|
|
599
|
-
}
|
|
600
|
-
function createSchemaUnsafe(input) {
|
|
601
|
-
const languages = normalizeLanguages(input.languages);
|
|
602
|
-
const tables = {};
|
|
603
|
-
for (const [tableName, fields] of Object.entries(input.tables)) {
|
|
604
|
-
tables[tableName] = {
|
|
605
|
-
name: tableName,
|
|
606
|
-
fields: Object.fromEntries(Object.entries(fields).map(([fieldName, builder]) => [
|
|
607
|
-
fieldName,
|
|
608
|
-
builder.build()
|
|
609
|
-
]))
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
return {
|
|
613
|
-
name: input.name,
|
|
614
|
-
languages,
|
|
615
|
-
tables
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
function mergeSchemas(schemas) {
|
|
619
|
-
if (schemas.length === 0) {
|
|
620
|
-
throw new Error("At least one schema is required");
|
|
621
|
-
}
|
|
622
|
-
const defaultLang = schemas[0].languages.default;
|
|
623
|
-
const allLangs = new Set;
|
|
624
|
-
for (const schema of schemas) {
|
|
625
|
-
for (const lang of schema.languages.supported) {
|
|
626
|
-
allLangs.add(lang);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
const tables = {};
|
|
630
|
-
for (const schema of schemas) {
|
|
631
|
-
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
632
|
-
if (tables[tableName]) {
|
|
633
|
-
throw new Error(`Duplicate table name across schemas: ${tableName}`);
|
|
634
|
-
}
|
|
635
|
-
tables[tableName] = table;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
return {
|
|
639
|
-
languages: {
|
|
640
|
-
default: defaultLang,
|
|
641
|
-
supported: Array.from(allLangs)
|
|
642
|
-
},
|
|
643
|
-
tables
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
492
|
// src/schema/helpers.ts
|
|
647
493
|
function singularize(word) {
|
|
648
494
|
const irregulars = {
|
|
@@ -722,6 +568,338 @@ function toTranslationTableName(tableName) {
|
|
|
722
568
|
function toTranslationFKName(tableName) {
|
|
723
569
|
return `${singularize(tableName)}_id`;
|
|
724
570
|
}
|
|
571
|
+
// src/adapters/RestAdapter.ts
|
|
572
|
+
class RestAdapter {
|
|
573
|
+
baseUrl;
|
|
574
|
+
databasePrefix;
|
|
575
|
+
token;
|
|
576
|
+
getTokenFn;
|
|
577
|
+
customHeaders;
|
|
578
|
+
fetchFn;
|
|
579
|
+
timeout;
|
|
580
|
+
schema;
|
|
581
|
+
defaultLang;
|
|
582
|
+
constructor(config) {
|
|
583
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
584
|
+
this.databasePrefix = config.databasePrefix ?? "/database";
|
|
585
|
+
this.token = config.token;
|
|
586
|
+
this.getTokenFn = config.getToken;
|
|
587
|
+
this.customHeaders = config.headers ?? {};
|
|
588
|
+
const gf = globalThis;
|
|
589
|
+
this.fetchFn = config.fetch ?? gf.fetch.bind(gf);
|
|
590
|
+
this.timeout = config.timeout ?? 30000;
|
|
591
|
+
this.schema = config.schema;
|
|
592
|
+
this.defaultLang = config.defaultLang;
|
|
593
|
+
}
|
|
594
|
+
buildUrl(path) {
|
|
595
|
+
return `${this.baseUrl}${this.databasePrefix}${path}`;
|
|
596
|
+
}
|
|
597
|
+
getHeaders() {
|
|
598
|
+
const headers = {
|
|
599
|
+
"Content-Type": "application/json",
|
|
600
|
+
Accept: "application/json",
|
|
601
|
+
...this.customHeaders
|
|
602
|
+
};
|
|
603
|
+
const token = this.getTokenFn ? this.getTokenFn() : this.token;
|
|
604
|
+
if (token) {
|
|
605
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
606
|
+
}
|
|
607
|
+
return headers;
|
|
608
|
+
}
|
|
609
|
+
async request(url, init) {
|
|
610
|
+
const controller = new AbortController;
|
|
611
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
612
|
+
try {
|
|
613
|
+
const response = await this.fetchFn(url, {
|
|
614
|
+
...init,
|
|
615
|
+
headers: { ...this.getHeaders(), ...init?.headers ?? {} },
|
|
616
|
+
signal: controller.signal
|
|
617
|
+
});
|
|
618
|
+
if (!response.ok) {
|
|
619
|
+
const body = await response.json().catch(() => null);
|
|
620
|
+
const message = body?.message || body?.error || `HTTP ${response.status} ${response.statusText}`;
|
|
621
|
+
throw new Error(message);
|
|
622
|
+
}
|
|
623
|
+
return await response.json();
|
|
624
|
+
} finally {
|
|
625
|
+
clearTimeout(timer);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
buildQueryParams(options) {
|
|
629
|
+
const params = new URLSearchParams;
|
|
630
|
+
if (!options)
|
|
631
|
+
return params;
|
|
632
|
+
if (options.where && Object.keys(options.where).length > 0) {
|
|
633
|
+
params.set("where", JSON.stringify(options.where));
|
|
634
|
+
}
|
|
635
|
+
if (options.orderBy?.length) {
|
|
636
|
+
params.set("order", options.orderBy.map((o) => `${o.field}:${o.direction}`).join(","));
|
|
637
|
+
}
|
|
638
|
+
if (options.limit != null) {
|
|
639
|
+
params.set("limit", String(options.limit));
|
|
640
|
+
}
|
|
641
|
+
if (options.offset != null) {
|
|
642
|
+
params.set("offset", String(options.offset));
|
|
643
|
+
}
|
|
644
|
+
if (options.lang) {
|
|
645
|
+
params.set("lang", options.lang);
|
|
646
|
+
}
|
|
647
|
+
if (options.fallbackLang) {
|
|
648
|
+
params.set("fallback_lang", options.fallbackLang);
|
|
649
|
+
}
|
|
650
|
+
return params;
|
|
651
|
+
}
|
|
652
|
+
urlWithParams(base, params) {
|
|
653
|
+
const qs = params.toString();
|
|
654
|
+
return qs ? `${base}?${qs}` : base;
|
|
655
|
+
}
|
|
656
|
+
setSchema(schema) {
|
|
657
|
+
this.schema = schema;
|
|
658
|
+
}
|
|
659
|
+
async connect() {
|
|
660
|
+
if (!this.schema)
|
|
661
|
+
return;
|
|
662
|
+
const apiSchema = {};
|
|
663
|
+
for (const [tableName, tableDef] of Object.entries(this.schema.tables)) {
|
|
664
|
+
const columns = [];
|
|
665
|
+
for (const [fieldName, fieldDef] of Object.entries(tableDef.fields)) {
|
|
666
|
+
const col = {
|
|
667
|
+
name: fieldName,
|
|
668
|
+
type: this.mapFieldType(fieldDef.type)
|
|
669
|
+
};
|
|
670
|
+
if (fieldDef.primary)
|
|
671
|
+
col.primary_key = true;
|
|
672
|
+
if (!fieldDef.nullable)
|
|
673
|
+
col.not_null = true;
|
|
674
|
+
if (fieldDef.unique)
|
|
675
|
+
col.unique = true;
|
|
676
|
+
if (fieldDef.default !== undefined)
|
|
677
|
+
col.default_value = String(fieldDef.default);
|
|
678
|
+
if (fieldDef.translatable)
|
|
679
|
+
col.translatable = true;
|
|
680
|
+
columns.push(col);
|
|
681
|
+
}
|
|
682
|
+
const tableEntry = { columns };
|
|
683
|
+
if (tableDef.permissions) {
|
|
684
|
+
tableEntry.permissions = tableDef.permissions;
|
|
685
|
+
}
|
|
686
|
+
apiSchema[tableName] = tableEntry;
|
|
687
|
+
}
|
|
688
|
+
await this.request(this.buildUrl("/generate"), {
|
|
689
|
+
method: "POST",
|
|
690
|
+
body: JSON.stringify({ schema: { tables: apiSchema } })
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
mapFieldType(type) {
|
|
694
|
+
switch (type) {
|
|
695
|
+
case "id":
|
|
696
|
+
case "int":
|
|
697
|
+
return "INTEGER";
|
|
698
|
+
case "string":
|
|
699
|
+
case "text":
|
|
700
|
+
case "timestamp":
|
|
701
|
+
return "TEXT";
|
|
702
|
+
case "decimal":
|
|
703
|
+
return "REAL";
|
|
704
|
+
case "bool":
|
|
705
|
+
return "INTEGER";
|
|
706
|
+
case "json":
|
|
707
|
+
case "object":
|
|
708
|
+
case "object[]":
|
|
709
|
+
case "string[]":
|
|
710
|
+
case "number[]":
|
|
711
|
+
case "boolean[]":
|
|
712
|
+
return "TEXT";
|
|
713
|
+
default:
|
|
714
|
+
return "TEXT";
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
close() {}
|
|
718
|
+
async list(table, options) {
|
|
719
|
+
const params = this.buildQueryParams(options);
|
|
720
|
+
const url = this.urlWithParams(this.buildUrl(`/${table}/list`), params);
|
|
721
|
+
const res = await this.request(url);
|
|
722
|
+
return res.data;
|
|
723
|
+
}
|
|
724
|
+
async get(table, id, options) {
|
|
725
|
+
const params = new URLSearchParams;
|
|
726
|
+
if (options?.lang)
|
|
727
|
+
params.set("lang", options.lang);
|
|
728
|
+
if (options?.fallbackLang)
|
|
729
|
+
params.set("fallback_lang", options.fallbackLang);
|
|
730
|
+
const url = this.urlWithParams(this.buildUrl(`/${table}/${id}`), params);
|
|
731
|
+
try {
|
|
732
|
+
const res = await this.request(url);
|
|
733
|
+
return res.data ?? null;
|
|
734
|
+
} catch (err) {
|
|
735
|
+
if (err instanceof Error && err.message.includes("404")) {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
throw err;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
async findOne(table, options) {
|
|
742
|
+
const results = await this.list(table, { ...options, limit: 1 });
|
|
743
|
+
return results[0] ?? null;
|
|
744
|
+
}
|
|
745
|
+
async count(table, options) {
|
|
746
|
+
const params = new URLSearchParams;
|
|
747
|
+
if (options?.where && Object.keys(options.where).length > 0) {
|
|
748
|
+
params.set("where", JSON.stringify(options.where));
|
|
749
|
+
}
|
|
750
|
+
const url = this.urlWithParams(this.buildUrl(`/${table}/count`), params);
|
|
751
|
+
const res = await this.request(url);
|
|
752
|
+
return res.count;
|
|
753
|
+
}
|
|
754
|
+
async paginate(table, page, limit, options) {
|
|
755
|
+
const offset = (page - 1) * limit;
|
|
756
|
+
const [total, data] = await Promise.all([
|
|
757
|
+
this.count(table, options),
|
|
758
|
+
this.list(table, { ...options, limit, offset })
|
|
759
|
+
]);
|
|
760
|
+
const totalPages = Math.ceil(total / limit);
|
|
761
|
+
return {
|
|
762
|
+
data,
|
|
763
|
+
page,
|
|
764
|
+
limit,
|
|
765
|
+
total,
|
|
766
|
+
totalPages,
|
|
767
|
+
hasMore: page < totalPages
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
async create(table, data) {
|
|
771
|
+
const url = this.buildUrl(`/${table}/create`);
|
|
772
|
+
const res = await this.request(url, {
|
|
773
|
+
method: "POST",
|
|
774
|
+
body: JSON.stringify({ data })
|
|
775
|
+
});
|
|
776
|
+
const result = res.data ?? {};
|
|
777
|
+
if (res.id !== undefined && !("id" in result)) {
|
|
778
|
+
result.id = res.id;
|
|
779
|
+
}
|
|
780
|
+
return result;
|
|
781
|
+
}
|
|
782
|
+
async update(table, id, data, options) {
|
|
783
|
+
const url = this.buildUrl(`/${table}/${id}`);
|
|
784
|
+
const body = { data };
|
|
785
|
+
if (options?.translations) {
|
|
786
|
+
body.translations = options.translations;
|
|
787
|
+
}
|
|
788
|
+
const res = await this.request(url, {
|
|
789
|
+
method: "PUT",
|
|
790
|
+
body: JSON.stringify(body)
|
|
791
|
+
});
|
|
792
|
+
return res.data;
|
|
793
|
+
}
|
|
794
|
+
async delete(table, id) {
|
|
795
|
+
const url = this.buildUrl(`/${table}/${id}`);
|
|
796
|
+
try {
|
|
797
|
+
const res = await this.request(url, {
|
|
798
|
+
method: "DELETE"
|
|
799
|
+
});
|
|
800
|
+
return res.deleted ?? true;
|
|
801
|
+
} catch (err) {
|
|
802
|
+
if (err instanceof Error && err.message.includes("404")) {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
throw err;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
async createMany(table, records, options) {
|
|
809
|
+
const url = this.buildUrl(`/${table}/create-batch`);
|
|
810
|
+
const res = await this.request(url, {
|
|
811
|
+
method: "POST",
|
|
812
|
+
body: JSON.stringify({
|
|
813
|
+
records,
|
|
814
|
+
ignore: options?.ignore ?? false,
|
|
815
|
+
no_atomic: options?.noAtomic ?? false
|
|
816
|
+
})
|
|
817
|
+
});
|
|
818
|
+
return { created: res.created, ids: res.ids };
|
|
819
|
+
}
|
|
820
|
+
async updateMany(table, updates, options) {
|
|
821
|
+
const url = this.buildUrl(`/${table}/update-batch`);
|
|
822
|
+
const res = await this.request(url, {
|
|
823
|
+
method: "POST",
|
|
824
|
+
body: JSON.stringify({
|
|
825
|
+
updates,
|
|
826
|
+
no_atomic: options?.noAtomic ?? false
|
|
827
|
+
})
|
|
828
|
+
});
|
|
829
|
+
return { updated: res.updated };
|
|
830
|
+
}
|
|
831
|
+
async deleteMany(table, ids, options) {
|
|
832
|
+
const url = this.buildUrl(`/${table}/delete-batch`);
|
|
833
|
+
const res = await this.request(url, {
|
|
834
|
+
method: "POST",
|
|
835
|
+
body: JSON.stringify({
|
|
836
|
+
ids,
|
|
837
|
+
no_atomic: options?.noAtomic ?? false
|
|
838
|
+
})
|
|
839
|
+
});
|
|
840
|
+
return { deleted: res.deleted };
|
|
841
|
+
}
|
|
842
|
+
async createWithTranslations(table, data, translations) {
|
|
843
|
+
const url = this.buildUrl(`/${table}/create`);
|
|
844
|
+
const res = await this.request(url, {
|
|
845
|
+
method: "POST",
|
|
846
|
+
body: JSON.stringify({ data, translations })
|
|
847
|
+
});
|
|
848
|
+
const result = res.data ?? {};
|
|
849
|
+
if (res.id !== undefined && !("id" in result)) {
|
|
850
|
+
result.id = res.id;
|
|
851
|
+
}
|
|
852
|
+
return result;
|
|
853
|
+
}
|
|
854
|
+
async upsertTranslation(table, id, lang, data) {
|
|
855
|
+
const url = this.buildUrl(`/${table}/${id}`);
|
|
856
|
+
await this.request(url, {
|
|
857
|
+
method: "PUT",
|
|
858
|
+
body: JSON.stringify({ data, lang })
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
async getTranslations(table, id) {
|
|
862
|
+
const translationTable = toTranslationTableName(table);
|
|
863
|
+
const fkName = toTranslationFKName(table);
|
|
864
|
+
const params = new URLSearchParams;
|
|
865
|
+
params.set("where", JSON.stringify({ [fkName]: id }));
|
|
866
|
+
const url = this.urlWithParams(this.buildUrl(`/${translationTable}/list`), params);
|
|
867
|
+
const res = await this.request(url);
|
|
868
|
+
return res.data;
|
|
869
|
+
}
|
|
870
|
+
async raw(query, params) {
|
|
871
|
+
const url = this.buildUrl("/exec");
|
|
872
|
+
const res = await this.request(url, {
|
|
873
|
+
method: "POST",
|
|
874
|
+
body: JSON.stringify({ sql: query, params: params ?? [] })
|
|
875
|
+
});
|
|
876
|
+
return res.data;
|
|
877
|
+
}
|
|
878
|
+
async execute(query, params) {
|
|
879
|
+
const url = this.buildUrl("/exec");
|
|
880
|
+
const res = await this.request(url, {
|
|
881
|
+
method: "POST",
|
|
882
|
+
body: JSON.stringify({ sql: query, params: params ?? [] })
|
|
883
|
+
});
|
|
884
|
+
return {
|
|
885
|
+
changes: res.changes,
|
|
886
|
+
lastInsertRowid: res.last_insert_rowid ?? 0
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
async beginTransaction() {}
|
|
890
|
+
async commit() {}
|
|
891
|
+
async rollback() {}
|
|
892
|
+
async getTables() {
|
|
893
|
+
const url = this.buildUrl("/tables");
|
|
894
|
+
const res = await this.request(url);
|
|
895
|
+
return res.tables;
|
|
896
|
+
}
|
|
897
|
+
async getTableSchema(table) {
|
|
898
|
+
const url = this.buildUrl(`/${table}/schema`);
|
|
899
|
+
const res = await this.request(url);
|
|
900
|
+
return res.columns;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
725
903
|
// src/utils/whereBuilder.ts
|
|
726
904
|
var COMPARISON_OPERATORS = {
|
|
727
905
|
$eq: "=",
|
|
@@ -1031,12 +1209,18 @@ function parseJSONSchemaWithWarnings(json) {
|
|
|
1031
1209
|
const tables = {};
|
|
1032
1210
|
for (const [tableName, jsonTable] of Object.entries(json.tables)) {
|
|
1033
1211
|
const fields = {};
|
|
1212
|
+
let permissions;
|
|
1034
1213
|
for (const [fieldName, jsonField] of Object.entries(jsonTable)) {
|
|
1214
|
+
if (fieldName === "$permissions") {
|
|
1215
|
+
permissions = jsonField;
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1035
1218
|
fields[fieldName] = convertJSONField(jsonField);
|
|
1036
1219
|
}
|
|
1037
1220
|
tables[tableName] = {
|
|
1038
1221
|
name: tableName,
|
|
1039
|
-
fields
|
|
1222
|
+
fields,
|
|
1223
|
+
...permissions ? { permissions } : {}
|
|
1040
1224
|
};
|
|
1041
1225
|
}
|
|
1042
1226
|
return {
|
|
@@ -1128,7 +1312,7 @@ function convertJSONField(jsonField) {
|
|
|
1128
1312
|
type,
|
|
1129
1313
|
nullable: jsonField.nullable ?? !jsonField.required,
|
|
1130
1314
|
unique: jsonField.unique ?? false,
|
|
1131
|
-
primary: jsonField.primary ??
|
|
1315
|
+
primary: jsonField.primary ?? type === "id",
|
|
1132
1316
|
default: jsonField.default,
|
|
1133
1317
|
translatable: jsonField.translatable ?? false,
|
|
1134
1318
|
properties,
|
|
@@ -1198,13 +1382,13 @@ export {
|
|
|
1198
1382
|
pluralize,
|
|
1199
1383
|
parseJSONSchemaWithWarnings,
|
|
1200
1384
|
parseJSONSchema,
|
|
1201
|
-
mergeSchemas,
|
|
1202
1385
|
isValidSchema,
|
|
1203
1386
|
isRequiredField,
|
|
1204
1387
|
isJsonType,
|
|
1205
1388
|
hasTranslatableFields,
|
|
1206
1389
|
getTranslationTableFields,
|
|
1207
1390
|
getTranslatableFields,
|
|
1391
|
+
getTablePermissions,
|
|
1208
1392
|
getRequiredFields,
|
|
1209
1393
|
getReferenceFields,
|
|
1210
1394
|
getRefTargetFull,
|
|
@@ -1214,11 +1398,8 @@ export {
|
|
|
1214
1398
|
getNonTranslatableFields,
|
|
1215
1399
|
getMainTableFields,
|
|
1216
1400
|
getInsertableFields,
|
|
1217
|
-
f,
|
|
1218
1401
|
extractTranslatableData,
|
|
1219
1402
|
deserializeRow,
|
|
1220
|
-
defineSchema,
|
|
1221
|
-
createSchemaUnsafe,
|
|
1222
1403
|
buildWhereClause,
|
|
1223
1404
|
buildTranslationUpsert,
|
|
1224
1405
|
buildTranslationQueryById,
|
|
@@ -1226,5 +1407,6 @@ export {
|
|
|
1226
1407
|
buildTranslationInsert,
|
|
1227
1408
|
assertValidSchema,
|
|
1228
1409
|
ValidationErrorCode,
|
|
1410
|
+
RestAdapter,
|
|
1229
1411
|
ORM
|
|
1230
1412
|
};
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema Module Exports
|
|
3
3
|
*/
|
|
4
|
-
export { defineSchema, f, mergeSchemas, createSchemaUnsafe } from "./defineSchema";
|
|
5
|
-
export type { FieldBuilder } from "./fieldBuilder";
|
|
6
4
|
export { validateSchema, assertValidSchema, isValidSchema, validateTable, ValidationErrorCode, } from "./validator";
|
|
7
5
|
export type { ValidationError } from "./validator";
|
|
8
6
|
export { singularize, pluralize, toPascalCase, toCamelCase, toSnakeCase, toInterfaceName, toDbInterfaceName, toPascalCasePlural, toTranslationTableName, toTranslationFKName, } from "./helpers";
|
|
9
|
-
export { getTranslatableFields, getNonTranslatableFields, getInsertableFields, hasTranslatableFields, getPrimaryKeyField, getReferenceFields, getMainTableFields, getTranslationTableFields, isRequiredField, getRequiredFields, getRefTarget, getRefTargetFull, } from "./schemaHelpers";
|
|
7
|
+
export { getTranslatableFields, getNonTranslatableFields, getInsertableFields, hasTranslatableFields, getPrimaryKeyField, getReferenceFields, getMainTableFields, getTranslationTableFields, isRequiredField, getRequiredFields, getRefTarget, getRefTargetFull, getTablePermissions, } from "./schemaHelpers";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Utilities for working with schema definitions.
|
|
5
5
|
*/
|
|
6
|
-
import type { TableDefinition, FieldDefinition, FieldReference } from "../types";
|
|
6
|
+
import type { TableDefinition, FieldDefinition, FieldReference, TablePermissions } from "../types";
|
|
7
7
|
/**
|
|
8
8
|
* Get names of translatable fields in a table
|
|
9
9
|
*/
|
|
@@ -53,3 +53,7 @@ export declare function getRefTarget(field: FieldDefinition): string | null;
|
|
|
53
53
|
* Returns { table, field } object
|
|
54
54
|
*/
|
|
55
55
|
export declare function getRefTargetFull(field: FieldDefinition): FieldReference | null;
|
|
56
|
+
/**
|
|
57
|
+
* Get table permissions (undefined if no restrictions)
|
|
58
|
+
*/
|
|
59
|
+
export declare function getTablePermissions(table: TableDefinition): TablePermissions | undefined;
|
|
@@ -24,6 +24,8 @@ export declare const ValidationErrorCode: {
|
|
|
24
24
|
readonly DUPLICATE_TABLE: "DUPLICATE_TABLE";
|
|
25
25
|
readonly RESERVED_FIELD_NAME: "RESERVED_FIELD_NAME";
|
|
26
26
|
readonly SELF_REFERENCE_ON_REQUIRED: "SELF_REFERENCE_ON_REQUIRED";
|
|
27
|
+
readonly INVALID_PERMISSION_ROLE: "INVALID_PERMISSION_ROLE";
|
|
28
|
+
readonly INVALID_PERMISSION_ACTION: "INVALID_PERMISSION_ACTION";
|
|
27
29
|
};
|
|
28
30
|
/**
|
|
29
31
|
* Validate a schema definition
|