@promakeai/orm 1.0.5 → 1.3.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/README.md +155 -185
- package/dist/adapters/RestAdapter.d.ts +94 -0
- package/dist/index.d.ts +14 -14
- package/dist/index.js +443 -197
- 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/dist/utils/jsonConverter.d.ts +5 -0
- package/package.json +1 -1
- package/src/adapters/RestAdapter.ts +483 -0
- package/src/index.ts +22 -21
- 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 +115 -7
- 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: "=",
|
|
@@ -1008,28 +1186,97 @@ function extractTranslatableData(data, tableSchema) {
|
|
|
1008
1186
|
return { mainData, translatableData };
|
|
1009
1187
|
}
|
|
1010
1188
|
// src/utils/jsonConverter.ts
|
|
1189
|
+
var LANGUAGE_NAME_TO_CODE = {
|
|
1190
|
+
english: "en",
|
|
1191
|
+
turkish: "tr",
|
|
1192
|
+
german: "de",
|
|
1193
|
+
french: "fr",
|
|
1194
|
+
spanish: "es",
|
|
1195
|
+
russian: "ru",
|
|
1196
|
+
arabic: "ar"
|
|
1197
|
+
};
|
|
1011
1198
|
function parseJSONSchema(json) {
|
|
1199
|
+
return parseJSONSchemaWithWarnings(json).schema;
|
|
1200
|
+
}
|
|
1201
|
+
function parseJSONSchemaWithWarnings(json) {
|
|
1202
|
+
const warnings = [];
|
|
1203
|
+
const supported = (json.languages || ["en"]).map((lang) => normalizeLangToken(lang));
|
|
1204
|
+
const defaultLanguage = resolveDefaultLanguage(json.defaultLanguage, supported, warnings);
|
|
1012
1205
|
const languages = {
|
|
1013
|
-
default:
|
|
1014
|
-
supported
|
|
1206
|
+
default: defaultLanguage,
|
|
1207
|
+
supported
|
|
1015
1208
|
};
|
|
1016
1209
|
const tables = {};
|
|
1017
1210
|
for (const [tableName, jsonTable] of Object.entries(json.tables)) {
|
|
1018
1211
|
const fields = {};
|
|
1212
|
+
let permissions;
|
|
1019
1213
|
for (const [fieldName, jsonField] of Object.entries(jsonTable)) {
|
|
1214
|
+
if (fieldName === "$permissions") {
|
|
1215
|
+
permissions = jsonField;
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1020
1218
|
fields[fieldName] = convertJSONField(jsonField);
|
|
1021
1219
|
}
|
|
1022
1220
|
tables[tableName] = {
|
|
1023
1221
|
name: tableName,
|
|
1024
|
-
fields
|
|
1222
|
+
fields,
|
|
1223
|
+
...permissions ? { permissions } : {}
|
|
1025
1224
|
};
|
|
1026
1225
|
}
|
|
1027
1226
|
return {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1227
|
+
schema: {
|
|
1228
|
+
name: json.name,
|
|
1229
|
+
languages,
|
|
1230
|
+
tables
|
|
1231
|
+
},
|
|
1232
|
+
warnings
|
|
1031
1233
|
};
|
|
1032
1234
|
}
|
|
1235
|
+
function normalizeLangToken(lang) {
|
|
1236
|
+
return String(lang).trim().toLowerCase();
|
|
1237
|
+
}
|
|
1238
|
+
function resolveDefaultLanguage(input, supported, warnings) {
|
|
1239
|
+
const fallback = supported[0] || "en";
|
|
1240
|
+
if (!input)
|
|
1241
|
+
return fallback;
|
|
1242
|
+
const normalizedInput = normalizeLangToken(input);
|
|
1243
|
+
const directMatch = supported.find((lang) => normalizeLangToken(lang) === normalizedInput);
|
|
1244
|
+
if (directMatch)
|
|
1245
|
+
return directMatch;
|
|
1246
|
+
const languageNameMatch = resolveLanguageNameToSupportedCode(normalizedInput, supported);
|
|
1247
|
+
if (languageNameMatch) {
|
|
1248
|
+
warnings.push(`Normalized defaultLanguage "${input}" to "${languageNameMatch}".`);
|
|
1249
|
+
return languageNameMatch;
|
|
1250
|
+
}
|
|
1251
|
+
throw new Error(`Invalid defaultLanguage "${input}". Expected one of: ${supported.join(", ")}`);
|
|
1252
|
+
}
|
|
1253
|
+
function resolveLanguageNameToSupportedCode(normalizedInput, supported) {
|
|
1254
|
+
const mapped = LANGUAGE_NAME_TO_CODE[normalizedInput];
|
|
1255
|
+
if (mapped) {
|
|
1256
|
+
const supportedMatch = supported.find((lang) => normalizeLangToken(lang) === mapped);
|
|
1257
|
+
if (supportedMatch)
|
|
1258
|
+
return supportedMatch;
|
|
1259
|
+
}
|
|
1260
|
+
for (const code of supported) {
|
|
1261
|
+
const languageName = getLanguageDisplayName(code);
|
|
1262
|
+
if (languageName && normalizeLangToken(languageName) === normalizedInput) {
|
|
1263
|
+
return code;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
return null;
|
|
1267
|
+
}
|
|
1268
|
+
function getLanguageDisplayName(code) {
|
|
1269
|
+
try {
|
|
1270
|
+
if (typeof Intl === "undefined" || !("DisplayNames" in Intl)) {
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
const displayNames = new Intl.DisplayNames(["en"], { type: "language" });
|
|
1274
|
+
const label = displayNames.of(code);
|
|
1275
|
+
return label || null;
|
|
1276
|
+
} catch {
|
|
1277
|
+
return null;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1033
1280
|
function normalizeJsonType(type) {
|
|
1034
1281
|
if (Array.isArray(type)) {
|
|
1035
1282
|
const first = type[0];
|
|
@@ -1065,7 +1312,7 @@ function convertJSONField(jsonField) {
|
|
|
1065
1312
|
type,
|
|
1066
1313
|
nullable: jsonField.nullable ?? !jsonField.required,
|
|
1067
1314
|
unique: jsonField.unique ?? false,
|
|
1068
|
-
primary: jsonField.primary ??
|
|
1315
|
+
primary: jsonField.primary ?? type === "id",
|
|
1069
1316
|
default: jsonField.default,
|
|
1070
1317
|
translatable: jsonField.translatable ?? false,
|
|
1071
1318
|
properties,
|
|
@@ -1133,14 +1380,15 @@ export {
|
|
|
1133
1380
|
serializeRow,
|
|
1134
1381
|
resolvePopulate,
|
|
1135
1382
|
pluralize,
|
|
1383
|
+
parseJSONSchemaWithWarnings,
|
|
1136
1384
|
parseJSONSchema,
|
|
1137
|
-
mergeSchemas,
|
|
1138
1385
|
isValidSchema,
|
|
1139
1386
|
isRequiredField,
|
|
1140
1387
|
isJsonType,
|
|
1141
1388
|
hasTranslatableFields,
|
|
1142
1389
|
getTranslationTableFields,
|
|
1143
1390
|
getTranslatableFields,
|
|
1391
|
+
getTablePermissions,
|
|
1144
1392
|
getRequiredFields,
|
|
1145
1393
|
getReferenceFields,
|
|
1146
1394
|
getRefTargetFull,
|
|
@@ -1150,11 +1398,8 @@ export {
|
|
|
1150
1398
|
getNonTranslatableFields,
|
|
1151
1399
|
getMainTableFields,
|
|
1152
1400
|
getInsertableFields,
|
|
1153
|
-
f,
|
|
1154
1401
|
extractTranslatableData,
|
|
1155
1402
|
deserializeRow,
|
|
1156
|
-
defineSchema,
|
|
1157
|
-
createSchemaUnsafe,
|
|
1158
1403
|
buildWhereClause,
|
|
1159
1404
|
buildTranslationUpsert,
|
|
1160
1405
|
buildTranslationQueryById,
|
|
@@ -1162,5 +1407,6 @@ export {
|
|
|
1162
1407
|
buildTranslationInsert,
|
|
1163
1408
|
assertValidSchema,
|
|
1164
1409
|
ValidationErrorCode,
|
|
1410
|
+
RestAdapter,
|
|
1165
1411
|
ORM
|
|
1166
1412
|
};
|