appflare 0.2.40 → 0.2.42
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/Documentation.md +108 -4
- package/cli/schema-compiler.ts +2 -0
- package/cli/templates/dashboard/builders/functions/tree-builder.ts +47 -0
- package/cli/templates/dashboard/builders/navigation.ts +55 -22
- package/cli/templates/dashboard/components/layout.ts +33 -1
- package/cli/templates/handlers/generators/types/query-definitions/filter-and-where-types.ts +22 -2
- package/cli/templates/handlers/generators/types/query-runtime/runtime-write.ts +275 -14
- package/dist/cli/index.js +700 -375
- package/dist/cli/index.mjs +700 -375
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/schema.ts +1 -1
|
@@ -561,6 +561,8 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
561
561
|
return rows;
|
|
562
562
|
},
|
|
563
563
|
update: async (args: QueryUpdateArgs<TableName>) => {
|
|
564
|
+
const transaction = ($db as any).transaction;
|
|
565
|
+
|
|
564
566
|
const whereFilter = buildWhereFilter(
|
|
565
567
|
table,
|
|
566
568
|
args.where as Record<string, unknown> | undefined,
|
|
@@ -569,27 +571,286 @@ export function generateQueryRuntimeWriteSection(): string {
|
|
|
569
571
|
|
|
570
572
|
const setPayload = args.set as Record<string, unknown>;
|
|
571
573
|
const cleanSetPayload: Record<string, unknown> = {};
|
|
574
|
+
const relationPayloads: Array<{
|
|
575
|
+
relationName: string;
|
|
576
|
+
relation: RuntimeRelation;
|
|
577
|
+
value: unknown;
|
|
578
|
+
}> = [];
|
|
579
|
+
|
|
572
580
|
for (const [key, value] of Object.entries(setPayload)) {
|
|
573
|
-
if (value
|
|
574
|
-
|
|
581
|
+
if (value === undefined) continue;
|
|
582
|
+
const runtimeRelation = getRuntimeRelation(tableName, key);
|
|
583
|
+
if (runtimeRelation && runtimeRelation.kind === "manyToMany") {
|
|
584
|
+
relationPayloads.push({
|
|
585
|
+
relationName: key,
|
|
586
|
+
relation: runtimeRelation,
|
|
587
|
+
value,
|
|
588
|
+
});
|
|
589
|
+
continue;
|
|
575
590
|
}
|
|
591
|
+
cleanSetPayload[key] = value;
|
|
576
592
|
}
|
|
577
593
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
594
|
+
const normalizeRelationValue = (
|
|
595
|
+
value: unknown,
|
|
596
|
+
): { items: unknown[]; mode: "merge" | "overwrite" } => {
|
|
597
|
+
if (Array.isArray(value)) {
|
|
598
|
+
return { items: value, mode: "merge" };
|
|
599
|
+
}
|
|
600
|
+
if (value && typeof value === "object") {
|
|
601
|
+
const record = value as Record<string, unknown>;
|
|
602
|
+
const items = Array.isArray(record.items) ? record.items : [];
|
|
603
|
+
const mode = record.mode === "overwrite" ? "overwrite" : "merge";
|
|
604
|
+
return { items, mode };
|
|
605
|
+
}
|
|
606
|
+
return { items: [], mode: "merge" };
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const executeUpdateWithRelations = async (
|
|
610
|
+
tx: any,
|
|
611
|
+
): Promise<Array<TableModel<TableName>>> => {
|
|
612
|
+
let updateQuery: any = tx
|
|
613
|
+
.update(table as any)
|
|
614
|
+
.set(cleanSetPayload as any);
|
|
581
615
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
616
|
+
if (whereFilter) {
|
|
617
|
+
updateQuery = updateQuery.where(whereFilter);
|
|
618
|
+
}
|
|
619
|
+
if (
|
|
620
|
+
typeof args.limit === "number" &&
|
|
621
|
+
typeof updateQuery.limit === "function"
|
|
622
|
+
) {
|
|
623
|
+
updateQuery = updateQuery.limit(args.limit);
|
|
624
|
+
}
|
|
625
|
+
if (typeof updateQuery.returning === "function") {
|
|
626
|
+
updateQuery = updateQuery.returning();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const rows = (await updateQuery) as Array<TableModel<TableName>>;
|
|
630
|
+
if (rows.length === 0) return rows;
|
|
631
|
+
|
|
632
|
+
for (const row of rows) {
|
|
633
|
+
const parentId = (row as Record<string, unknown>)["id"];
|
|
634
|
+
if (parentId === undefined || parentId === null) continue;
|
|
635
|
+
|
|
636
|
+
for (const { relationName, relation, value } of relationPayloads) {
|
|
637
|
+
const { items, mode } = normalizeRelationValue(value);
|
|
638
|
+
|
|
639
|
+
const junctionTable = (mergedSchema as Record<string, unknown>)[
|
|
640
|
+
relation.junctionTable
|
|
641
|
+
];
|
|
642
|
+
if (!junctionTable) {
|
|
643
|
+
throw new Error(
|
|
644
|
+
"Unknown junction table '" +
|
|
645
|
+
relation.junctionTable +
|
|
646
|
+
"' for relation '" +
|
|
647
|
+
tableName +
|
|
648
|
+
"." +
|
|
649
|
+
relationName +
|
|
650
|
+
"'.",
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const sourceField = relation.sourceField;
|
|
655
|
+
const targetField = relation.targetField;
|
|
656
|
+
if (!sourceField || !targetField) {
|
|
657
|
+
throw new Error(
|
|
658
|
+
"Relation '" +
|
|
659
|
+
tableName +
|
|
660
|
+
"." +
|
|
661
|
+
relationName +
|
|
662
|
+
"' is missing junction metadata fields.",
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const parentReferenceField = relation.referenceField ?? "id";
|
|
667
|
+
const parentReferenceColumn = (table as Record<string, unknown>)[parentReferenceField];
|
|
668
|
+
if (!parentReferenceColumn) {
|
|
669
|
+
throw new Error(
|
|
670
|
+
"Table '" +
|
|
671
|
+
tableName +
|
|
672
|
+
"' is missing column '" +
|
|
673
|
+
parentReferenceField +
|
|
674
|
+
"' for relation '" +
|
|
675
|
+
relationName +
|
|
676
|
+
"'.",
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const junctionSourceColumn = (junctionTable as Record<string, unknown>)[sourceField];
|
|
681
|
+
if (!junctionSourceColumn) {
|
|
682
|
+
throw new Error(
|
|
683
|
+
"Junction table '" +
|
|
684
|
+
relation.junctionTable +
|
|
685
|
+
"' is missing column '" +
|
|
686
|
+
sourceField +
|
|
687
|
+
"' for relation '" +
|
|
688
|
+
relationName +
|
|
689
|
+
"'.",
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (mode === "overwrite") {
|
|
694
|
+
await tx
|
|
695
|
+
.delete(junctionTable as any)
|
|
696
|
+
.where(eq(junctionSourceColumn as any, (row as Record<string, unknown>)[parentReferenceField] as any));
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const referenceField = relation.targetReferenceField ?? "id";
|
|
700
|
+
const targetTable = (mergedSchema as Record<string, unknown>)[
|
|
701
|
+
relation.targetTable
|
|
702
|
+
];
|
|
703
|
+
|
|
704
|
+
const junctionTargetColumn =
|
|
705
|
+
(junctionTable as Record<string, unknown>)[targetField];
|
|
706
|
+
if (!junctionTargetColumn) {
|
|
707
|
+
throw new Error(
|
|
708
|
+
"Junction table '" +
|
|
709
|
+
relation.junctionTable +
|
|
710
|
+
"' is missing column '" +
|
|
711
|
+
targetField +
|
|
712
|
+
"'.",
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
for (const item of items) {
|
|
717
|
+
let targetId: unknown;
|
|
718
|
+
|
|
719
|
+
if (
|
|
720
|
+
typeof item === "string" ||
|
|
721
|
+
typeof item === "number" ||
|
|
722
|
+
typeof item === "bigint"
|
|
723
|
+
) {
|
|
724
|
+
targetId = item;
|
|
725
|
+
} else if (item && typeof item === "object") {
|
|
726
|
+
const record = item as Record<string, unknown>;
|
|
727
|
+
const existingId = record[referenceField];
|
|
728
|
+
if (existingId !== undefined && existingId !== null) {
|
|
729
|
+
targetId = existingId;
|
|
730
|
+
} else {
|
|
731
|
+
if (!targetTable) {
|
|
732
|
+
throw new Error(
|
|
733
|
+
"Unknown target table '" +
|
|
734
|
+
relation.targetTable +
|
|
735
|
+
"' for relation '" +
|
|
736
|
+
tableName +
|
|
737
|
+
"." +
|
|
738
|
+
relationName +
|
|
739
|
+
"'.",
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
let createQuery: any = tx
|
|
743
|
+
.insert(targetTable as any)
|
|
744
|
+
.values(record as any);
|
|
745
|
+
if (typeof createQuery.returning === "function") {
|
|
746
|
+
createQuery = createQuery.returning();
|
|
747
|
+
}
|
|
748
|
+
const createdRows =
|
|
749
|
+
(await createQuery) as Array<Record<string, unknown>>;
|
|
750
|
+
const created = createdRows[0];
|
|
751
|
+
if (!created) {
|
|
752
|
+
throw new Error(
|
|
753
|
+
"Failed to create relation target for '" +
|
|
754
|
+
tableName +
|
|
755
|
+
"." +
|
|
756
|
+
relationName +
|
|
757
|
+
"'.",
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
targetId = created[referenceField];
|
|
761
|
+
if (targetId === undefined || targetId === null) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
"Created relation target for '" +
|
|
764
|
+
tableName +
|
|
765
|
+
"." +
|
|
766
|
+
relationName +
|
|
767
|
+
"' is missing '" +
|
|
768
|
+
referenceField +
|
|
769
|
+
"'.",
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
} else {
|
|
774
|
+
throw new Error(
|
|
775
|
+
"Relation '" +
|
|
776
|
+
tableName +
|
|
777
|
+
"." +
|
|
778
|
+
relationName +
|
|
779
|
+
"' expects an id or object payload.",
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (mode === "merge") {
|
|
784
|
+
const parentRefValue = (row as Record<string, unknown>)[parentReferenceField];
|
|
785
|
+
const existingLinks = await tx
|
|
786
|
+
.select()
|
|
787
|
+
.from(junctionTable as any)
|
|
788
|
+
.where(
|
|
789
|
+
and(
|
|
790
|
+
eq(junctionSourceColumn as any, parentRefValue as any),
|
|
791
|
+
eq(junctionTargetColumn as any, targetId as any),
|
|
792
|
+
),
|
|
793
|
+
);
|
|
794
|
+
if (existingLinks.length > 0) continue;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const parentRefValue = (row as Record<string, unknown>)[parentReferenceField];
|
|
798
|
+
await tx.insert(junctionTable as any).values({
|
|
799
|
+
[sourceField]: parentRefValue,
|
|
800
|
+
[targetField]: targetId,
|
|
801
|
+
} as any);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return rows;
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
let rows: Array<TableModel<TableName>>;
|
|
810
|
+
if (relationPayloads.length > 0) {
|
|
811
|
+
if (typeof transaction === "function") {
|
|
812
|
+
try {
|
|
813
|
+
rows = await transaction.call($db, (tx: any) =>
|
|
814
|
+
executeUpdateWithRelations(tx),
|
|
815
|
+
);
|
|
816
|
+
} catch (error) {
|
|
817
|
+
const message =
|
|
818
|
+
error instanceof Error ? error.message : String(error);
|
|
819
|
+
const lowered = message.toLowerCase();
|
|
820
|
+
if (
|
|
821
|
+
lowered.includes("failed query: begin") ||
|
|
822
|
+
lowered.includes('near "begin"') ||
|
|
823
|
+
lowered.includes("cannot start a transaction")
|
|
824
|
+
) {
|
|
825
|
+
rows = await executeUpdateWithRelations($db as any);
|
|
826
|
+
} else {
|
|
827
|
+
throw error;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
} else {
|
|
831
|
+
rows = await executeUpdateWithRelations($db as any);
|
|
832
|
+
}
|
|
833
|
+
} else {
|
|
834
|
+
let updateQuery: any = ($db as any)
|
|
835
|
+
.update(table as any)
|
|
836
|
+
.set(cleanSetPayload as any);
|
|
837
|
+
|
|
838
|
+
if (whereFilter) {
|
|
839
|
+
updateQuery = updateQuery.where(whereFilter);
|
|
840
|
+
}
|
|
841
|
+
if (
|
|
842
|
+
typeof args.limit === "number" &&
|
|
843
|
+
typeof updateQuery.limit === "function"
|
|
844
|
+
) {
|
|
845
|
+
updateQuery = updateQuery.limit(args.limit);
|
|
846
|
+
}
|
|
847
|
+
if (typeof updateQuery.returning === "function") {
|
|
848
|
+
updateQuery = updateQuery.returning();
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
rows = (await updateQuery) as Array<TableModel<TableName>>;
|
|
590
852
|
}
|
|
591
853
|
|
|
592
|
-
const rows = (await updateQuery) as Array<TableModel<TableName>>;
|
|
593
854
|
emitMutation(
|
|
594
855
|
"update",
|
|
595
856
|
{
|