@smartive/graphql-magic 19.3.0 → 19.3.1-next.2

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
- # [19.3.0](https://github.com/smartive/graphql-magic/compare/v19.2.1...v19.3.0) (2025-06-26)
1
+ ## [19.3.1-next.2](https://github.com/smartive/graphql-magic/compare/v19.3.1-next.1...v19.3.1-next.2) (2025-07-02)
2
2
 
3
3
 
4
- ### Features
4
+ ### Bug Fixes
5
5
 
6
- * Restore permissions ([#316](https://github.com/smartive/graphql-magic/issues/316)) ([536fcd5](https://github.com/smartive/graphql-magic/commit/536fcd54c5953acebf470f18ec2292e2090bec19))
6
+ * Sort results ([ed5f07f](https://github.com/smartive/graphql-magic/commit/ed5f07fd4ee9511ac7541370b1b1ba076a6ea960))
@@ -2682,62 +2682,60 @@ var applySubQueries = async (node, entries, parentVerifiedPermissionStacks) => {
2682
2682
  };
2683
2683
 
2684
2684
  // src/resolvers/mutations.ts
2685
- var mutationResolver = async (_parent, args2, partialCtx, info) => {
2686
- return await partialCtx.knex.transaction(async (knex) => {
2687
- const [, mutation, modelName] = it(info.fieldName.match(/^(create|update|delete|restore)(.+)$/));
2688
- const ctx = { ...partialCtx, knex, info, aliases: new AliasGenerator() };
2689
- switch (mutation) {
2690
- case "create": {
2691
- const id = await createEntity(modelName, args2.data, ctx, "mutation");
2692
- return await resolve(ctx, id);
2693
- }
2694
- case "update": {
2695
- const id = args2.where.id;
2696
- await updateEntity(modelName, id, args2.data, ctx, "mutation");
2697
- return await resolve(ctx, id);
2698
- }
2699
- case "delete": {
2700
- const id = args2.where.id;
2701
- await deleteEntity(modelName, id, ctx, {
2702
- dryRun: args2.dryRun,
2703
- trigger: "mutation"
2704
- });
2705
- return id;
2706
- }
2707
- case "restore": {
2708
- const id = args2.where.id;
2709
- await restoreEntity(modelName, id, ctx, "mutation");
2710
- return id;
2711
- }
2685
+ var withTransaction = async (ctx, fn) => await ctx.knex.transaction(async (knex) => fn({ ...ctx, knex }));
2686
+ var mutationResolver = async (_parent, args2, partialCtx, info) => withTransaction({ ...partialCtx, info, aliases: new AliasGenerator() }, async (ctx) => {
2687
+ const [, mutation, modelName] = it(info.fieldName.match(/^(create|update|delete|restore)(.+)$/));
2688
+ switch (mutation) {
2689
+ case "create": {
2690
+ const id = await createEntity(modelName, args2.data, ctx, "mutation");
2691
+ return await resolve(ctx, id);
2692
+ }
2693
+ case "update": {
2694
+ const id = args2.where.id;
2695
+ await updateEntity(modelName, id, args2.data, ctx, "mutation");
2696
+ return await resolve(ctx, id);
2697
+ }
2698
+ case "delete": {
2699
+ const id = args2.where.id;
2700
+ await deleteEntity(modelName, id, ctx, {
2701
+ dryRun: args2.dryRun,
2702
+ trigger: "mutation"
2703
+ });
2704
+ return id;
2712
2705
  }
2713
- });
2714
- };
2715
- var createEntity = async (modelName, input2, ctx, trigger = "direct-call") => {
2716
- const model = ctx.models.getModel(modelName, "entity");
2706
+ case "restore": {
2707
+ const id = args2.where.id;
2708
+ await restoreEntity(modelName, id, ctx, "mutation");
2709
+ return id;
2710
+ }
2711
+ }
2712
+ });
2713
+ var createEntity = async (modelName, input2, ctx, trigger = "direct-call") => withTransaction(ctx, async (ctx2) => {
2714
+ const model = ctx2.models.getModel(modelName, "entity");
2717
2715
  const normalizedInput = { ...input2 };
2718
2716
  if (!normalizedInput.id) {
2719
2717
  normalizedInput.id = (0, import_uuid.v4)();
2720
2718
  }
2721
2719
  const id = normalizedInput.id;
2722
2720
  if (!normalizedInput.createdAt) {
2723
- normalizedInput.createdAt = ctx.now;
2721
+ normalizedInput.createdAt = ctx2.now;
2724
2722
  }
2725
2723
  if (!normalizedInput.createdById) {
2726
- normalizedInput.createdById = ctx.user?.id;
2724
+ normalizedInput.createdById = ctx2.user?.id;
2727
2725
  }
2728
2726
  if (model.parent) {
2729
2727
  normalizedInput.type = model.name;
2730
2728
  }
2731
- sanitize(ctx, model, normalizedInput);
2732
- await checkCanWrite(ctx, model, normalizedInput, "CREATE");
2733
- await ctx.handleUploads?.(normalizedInput);
2734
- await ctx.mutationHook?.({
2729
+ sanitize(ctx2, model, normalizedInput);
2730
+ await checkCanWrite(ctx2, model, normalizedInput, "CREATE");
2731
+ await ctx2.handleUploads?.(normalizedInput);
2732
+ await ctx2.mutationHook?.({
2735
2733
  model,
2736
2734
  action: "create",
2737
2735
  trigger,
2738
2736
  when: "before",
2739
2737
  data: { prev: {}, input: input2, normalizedInput, next: normalizedInput },
2740
- ctx
2738
+ ctx: ctx2
2741
2739
  });
2742
2740
  if (model.parent) {
2743
2741
  const rootInput = {};
@@ -2752,75 +2750,75 @@ var createEntity = async (modelName, input2, ctx, trigger = "direct-call") => {
2752
2750
  }
2753
2751
  }
2754
2752
  }
2755
- await ctx.knex(model.parent).insert(rootInput);
2756
- await ctx.knex(model.name).insert(childInput);
2753
+ await ctx2.knex(model.parent).insert(rootInput);
2754
+ await ctx2.knex(model.name).insert(childInput);
2757
2755
  } else {
2758
- await ctx.knex(model.name).insert(normalizedInput);
2756
+ await ctx2.knex(model.name).insert(normalizedInput);
2759
2757
  }
2760
- await createRevision(model, normalizedInput, ctx);
2761
- await ctx.mutationHook?.({
2758
+ await createRevision(model, normalizedInput, ctx2);
2759
+ await ctx2.mutationHook?.({
2762
2760
  model,
2763
2761
  action: "create",
2764
2762
  trigger,
2765
2763
  when: "after",
2766
2764
  data: { prev: {}, input: input2, normalizedInput, next: normalizedInput },
2767
- ctx
2765
+ ctx: ctx2
2768
2766
  });
2769
2767
  return normalizedInput.id;
2770
- };
2771
- var updateEntities = async (modelName, where, updateFields, ctx) => {
2772
- const entities = await ctx.knex(modelName).where(where).select("id");
2768
+ });
2769
+ var updateEntities = async (modelName, where, updateFields, ctx) => withTransaction(ctx, async (ctx2) => {
2770
+ const entities = await ctx2.knex(modelName).where(where).select("id");
2773
2771
  for (const entity of entities) {
2774
- await updateEntity(modelName, entity.id, updateFields, ctx);
2772
+ await updateEntity(modelName, entity.id, updateFields, ctx2);
2775
2773
  }
2776
- };
2777
- var updateEntity = async (modelName, id, input2, ctx, trigger = "direct-call") => {
2778
- const model = ctx.models.getModel(modelName, "entity");
2774
+ });
2775
+ var updateEntity = async (modelName, id, input2, ctx, trigger = "direct-call") => withTransaction(ctx, async (ctx2) => {
2776
+ const model = ctx2.models.getModel(modelName, "entity");
2779
2777
  const normalizedInput = { ...input2 };
2780
- sanitize(ctx, model, normalizedInput);
2781
- const currentEntity = await getEntityToMutate(ctx, model, { id }, "UPDATE");
2778
+ sanitize(ctx2, model, normalizedInput);
2779
+ const currentEntity = await getEntityToMutate(ctx2, model, { id }, "UPDATE");
2782
2780
  for (const key of Object.keys(normalizedInput)) {
2783
2781
  if (normalizedInput[key] === currentEntity[key]) {
2784
2782
  delete normalizedInput[key];
2785
2783
  }
2786
2784
  }
2787
2785
  if (Object.keys(normalizedInput).length > 0) {
2788
- await checkCanWrite(ctx, model, normalizedInput, "UPDATE");
2789
- await ctx.handleUploads?.(normalizedInput);
2790
- await ctx.mutationHook?.({
2786
+ await checkCanWrite(ctx2, model, normalizedInput, "UPDATE");
2787
+ await ctx2.handleUploads?.(normalizedInput);
2788
+ await ctx2.mutationHook?.({
2791
2789
  model,
2792
2790
  action: "update",
2793
2791
  trigger,
2794
2792
  when: "before",
2795
2793
  data: { prev: currentEntity, input: input2, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
2796
- ctx
2794
+ ctx: ctx2
2797
2795
  });
2798
- await doUpdate(model, currentEntity, normalizedInput, ctx);
2799
- await ctx.mutationHook?.({
2796
+ await doUpdate(model, currentEntity, normalizedInput, ctx2);
2797
+ await ctx2.mutationHook?.({
2800
2798
  model,
2801
2799
  action: "update",
2802
2800
  trigger,
2803
2801
  when: "after",
2804
2802
  data: { prev: currentEntity, input: input2, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
2805
- ctx
2803
+ ctx: ctx2
2806
2804
  });
2807
2805
  }
2808
- };
2809
- var deleteEntities = async (modelName, where, ctx) => {
2810
- const entities = await ctx.knex(modelName).where(where).select("id");
2806
+ });
2807
+ var deleteEntities = async (modelName, where, ctx) => withTransaction(ctx, async (ctx2) => {
2808
+ const entities = await ctx2.knex(modelName).where(where).select("id");
2811
2809
  for (const entity of entities) {
2812
- await deleteEntity(modelName, entity.id, ctx, {
2810
+ await deleteEntity(modelName, entity.id, ctx2, {
2813
2811
  trigger: "direct-call"
2814
2812
  });
2815
2813
  }
2816
- };
2814
+ });
2817
2815
  var deleteEntity = async (modelName, id, ctx, {
2818
2816
  dryRun = false,
2819
2817
  trigger = "direct-call"
2820
- } = {}) => {
2821
- const model = ctx.models.getModel(modelName, "entity");
2818
+ } = {}) => withTransaction(ctx, async (ctx2) => {
2819
+ const model = ctx2.models.getModel(modelName, "entity");
2822
2820
  const rootModel = model.rootModel;
2823
- const entity = await getEntityToMutate(ctx, rootModel, { id }, "DELETE");
2821
+ const entity = await getEntityToMutate(ctx2, rootModel, { id }, "DELETE");
2824
2822
  if (entity.deleted) {
2825
2823
  throw new ForbiddenError(`${getTechnicalDisplay(model, entity)} is already deleted.`);
2826
2824
  }
@@ -2830,7 +2828,7 @@ var deleteEntity = async (modelName, id, ctx, {
2830
2828
  const beforeHooks = [];
2831
2829
  const mutations = [];
2832
2830
  const afterHooks = [];
2833
- const mutationHook = ctx.mutationHook;
2831
+ const mutationHook = ctx2.mutationHook;
2834
2832
  const deleteCascade = async (currentModel, currentEntity, currentTrigger) => {
2835
2833
  if (!(currentModel.name in toDelete)) {
2836
2834
  toDelete[currentModel.name] = {};
@@ -2838,12 +2836,12 @@ var deleteEntity = async (modelName, id, ctx, {
2838
2836
  if (currentEntity.id in toDelete[currentModel.name]) {
2839
2837
  return;
2840
2838
  }
2841
- toDelete[currentModel.name][currentEntity.id] = await fetchDisplay(ctx.knex, currentModel, currentEntity);
2839
+ toDelete[currentModel.name][currentEntity.id] = await fetchDisplay(ctx2.knex, currentModel, currentEntity);
2842
2840
  if (!dryRun) {
2843
2841
  const normalizedInput = {
2844
2842
  deleted: true,
2845
- deletedAt: ctx.now,
2846
- deletedById: ctx.user?.id,
2843
+ deletedAt: ctx2.now,
2844
+ deletedById: ctx2.user?.id,
2847
2845
  deleteRootType: rootModel.name,
2848
2846
  deleteRootId: entity.id
2849
2847
  };
@@ -2855,12 +2853,12 @@ var deleteEntity = async (modelName, id, ctx, {
2855
2853
  trigger: currentTrigger,
2856
2854
  when: "before",
2857
2855
  data: { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
2858
- ctx
2856
+ ctx: ctx2
2859
2857
  });
2860
2858
  });
2861
2859
  }
2862
2860
  mutations.push(async () => {
2863
- await doUpdate(currentModel, currentEntity, normalizedInput, ctx);
2861
+ await doUpdate(currentModel, currentEntity, normalizedInput, ctx2);
2864
2862
  });
2865
2863
  if (mutationHook) {
2866
2864
  afterHooks.push(async () => {
@@ -2870,7 +2868,7 @@ var deleteEntity = async (modelName, id, ctx, {
2870
2868
  trigger: currentTrigger,
2871
2869
  when: "after",
2872
2870
  data: { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
2873
- ctx
2871
+ ctx: ctx2
2874
2872
  });
2875
2873
  });
2876
2874
  }
@@ -2879,7 +2877,7 @@ var deleteEntity = async (modelName, id, ctx, {
2879
2877
  targetModel: descendantModel,
2880
2878
  field: { name: name2, foreignKey, onDelete }
2881
2879
  } of currentModel.reverseRelations.filter((reverseRelation) => !reverseRelation.field.inherited)) {
2882
- const query = ctx.knex(descendantModel.name).where({ [foreignKey]: currentEntity.id, deleted: false });
2880
+ const query = ctx2.knex(descendantModel.name).where({ [foreignKey]: currentEntity.id, deleted: false }).orderBy("createdAt", "asc").orderBy("id", "asc");
2883
2881
  const descendants = await query;
2884
2882
  if (descendants.length) {
2885
2883
  switch (onDelete) {
@@ -2891,7 +2889,7 @@ var deleteEntity = async (modelName, id, ctx, {
2891
2889
  }
2892
2890
  if (!toUnlink[descendantModel.name][descendant.id]) {
2893
2891
  toUnlink[descendantModel.name][descendant.id] = {
2894
- display: await fetchDisplay(ctx.knex, descendantModel, descendant),
2892
+ display: await fetchDisplay(ctx2.knex, descendantModel, descendant),
2895
2893
  fields: []
2896
2894
  };
2897
2895
  }
@@ -2906,12 +2904,12 @@ var deleteEntity = async (modelName, id, ctx, {
2906
2904
  trigger: "set-null",
2907
2905
  when: "before",
2908
2906
  data: { prev: descendant, input: {}, normalizedInput, next: { ...descendant, ...normalizedInput } },
2909
- ctx
2907
+ ctx: ctx2
2910
2908
  });
2911
2909
  });
2912
2910
  }
2913
2911
  mutations.push(async () => {
2914
- await doUpdate(descendantModel, descendant, normalizedInput, ctx);
2912
+ await doUpdate(descendantModel, descendant, normalizedInput, ctx2);
2915
2913
  });
2916
2914
  if (mutationHook) {
2917
2915
  afterHooks.push(async () => {
@@ -2921,7 +2919,7 @@ var deleteEntity = async (modelName, id, ctx, {
2921
2919
  trigger: "set-null",
2922
2920
  when: "after",
2923
2921
  data: { prev: descendant, input: {}, normalizedInput, next: { ...descendant, ...normalizedInput } },
2924
- ctx
2922
+ ctx: ctx2
2925
2923
  });
2926
2924
  });
2927
2925
  }
@@ -2936,7 +2934,7 @@ var deleteEntity = async (modelName, id, ctx, {
2936
2934
  for (const descendant of descendants) {
2937
2935
  if (!restricted[descendantModel.name][descendant.id]) {
2938
2936
  restricted[descendantModel.name][descendant.id] = {
2939
- display: await fetchDisplay(ctx.knex, descendantModel, descendant),
2937
+ display: await fetchDisplay(ctx2.knex, descendantModel, descendant),
2940
2938
  fields: [name2]
2941
2939
  };
2942
2940
  }
@@ -2955,7 +2953,7 @@ var deleteEntity = async (modelName, id, ctx, {
2955
2953
  `${getTechnicalDisplay(model, entity)} depends on ${getTechnicalDisplay(descendantModel, descendants[0])}${descendants.length > 1 ? ` (among others)` : ""} which cannot be deleted.`
2956
2954
  );
2957
2955
  }
2958
- applyPermissions(ctx, descendantModel.name, descendantModel.name, query, "DELETE");
2956
+ applyPermissions(ctx2, descendantModel.name, descendantModel.name, query, "DELETE");
2959
2957
  const deletableDescendants = await query;
2960
2958
  const notDeletableDescendants = descendants.filter(
2961
2959
  (descendant) => !deletableDescendants.some((d) => d.id === descendant.id)
@@ -2986,11 +2984,11 @@ var deleteEntity = async (modelName, id, ctx, {
2986
2984
  restricted
2987
2985
  });
2988
2986
  }
2989
- };
2990
- var restoreEntity = async (modelName, id, ctx, trigger = "direct-call") => {
2991
- const model = ctx.models.getModel(modelName, "entity");
2987
+ });
2988
+ var restoreEntity = async (modelName, id, ctx, trigger = "direct-call") => withTransaction(ctx, async (ctx2) => {
2989
+ const model = ctx2.models.getModel(modelName, "entity");
2992
2990
  const rootModel = model.rootModel;
2993
- const entity = await getEntityToMutate(ctx, rootModel, { id }, "RESTORE");
2991
+ const entity = await getEntityToMutate(ctx2, rootModel, { id }, "RESTORE");
2994
2992
  if (!entity.deleted) {
2995
2993
  throw new ForbiddenError(`${getTechnicalDisplay(model, entity)} is not deleted.`);
2996
2994
  }
@@ -3010,7 +3008,7 @@ var restoreEntity = async (modelName, id, ctx, trigger = "direct-call") => {
3010
3008
  if (!(currentEntity.deleteRootType === model.name && currentEntity.deleteRootId === entity.id)) {
3011
3009
  return;
3012
3010
  }
3013
- } else if (!anyDateToLuxon(currentEntity.deletedAt, ctx.timeZone).equals(anyDateToLuxon(entity.deletedAt, ctx.timeZone))) {
3011
+ } else if (!anyDateToLuxon(currentEntity.deletedAt, ctx2.timeZone).equals(anyDateToLuxon(entity.deletedAt, ctx2.timeZone))) {
3014
3012
  return;
3015
3013
  }
3016
3014
  if (!(currentModel.name in toRestore)) {
@@ -3024,15 +3022,15 @@ var restoreEntity = async (modelName, id, ctx, trigger = "direct-call") => {
3024
3022
  deleteRootType: null,
3025
3023
  deleteRootId: null
3026
3024
  };
3027
- if (ctx.mutationHook) {
3025
+ if (ctx2.mutationHook) {
3028
3026
  beforeHooks.push(async () => {
3029
- await ctx.mutationHook({
3027
+ await ctx2.mutationHook({
3030
3028
  model: currentModel,
3031
3029
  action: "restore",
3032
3030
  trigger: currentTrigger,
3033
3031
  when: "before",
3034
3032
  data: { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
3035
- ctx
3033
+ ctx: ctx2
3036
3034
  });
3037
3035
  });
3038
3036
  }
@@ -3045,24 +3043,24 @@ var restoreEntity = async (modelName, id, ctx, trigger = "direct-call") => {
3045
3043
  if (toRestore[relation.targetModel.name]?.has(parentId)) {
3046
3044
  continue;
3047
3045
  }
3048
- const parent = await ctx.knex(relation.targetModel.name).where({ id: parentId }).first();
3046
+ const parent = await ctx2.knex(relation.targetModel.name).where({ id: parentId }).first();
3049
3047
  if (parent?.deleted) {
3050
3048
  throw new ForbiddenError(
3051
3049
  `Can't restore ${getTechnicalDisplay(model, entity)} because it depends on deleted ${relation.targetModel.name} ${parentId}.`
3052
3050
  );
3053
3051
  }
3054
3052
  }
3055
- await doUpdate(currentModel, currentEntity, normalizedInput, ctx);
3053
+ await doUpdate(currentModel, currentEntity, normalizedInput, ctx2);
3056
3054
  });
3057
- if (ctx.mutationHook) {
3055
+ if (ctx2.mutationHook) {
3058
3056
  afterHooks.push(async () => {
3059
- await ctx.mutationHook({
3057
+ await ctx2.mutationHook({
3060
3058
  model: currentModel,
3061
3059
  action: "restore",
3062
3060
  trigger: currentTrigger,
3063
3061
  when: "after",
3064
3062
  data: { prev: currentEntity, input: {}, normalizedInput, next: { ...currentEntity, ...normalizedInput } },
3065
- ctx
3063
+ ctx: ctx2
3066
3064
  });
3067
3065
  });
3068
3066
  }
@@ -3070,14 +3068,14 @@ var restoreEntity = async (modelName, id, ctx, trigger = "direct-call") => {
3070
3068
  targetModel: descendantModel,
3071
3069
  field: { foreignKey }
3072
3070
  } of currentModel.reverseRelations.filter((reverseRelation) => !reverseRelation.field.inherited).filter(({ targetModel: { deletable } }) => deletable)) {
3073
- const query = ctx.knex(descendantModel.name).where({ [foreignKey]: currentEntity.id, deleted: true });
3071
+ const query = ctx2.knex(descendantModel.name).where({ [foreignKey]: currentEntity.id, deleted: true }).orderBy("createdAt", "asc").orderBy("id", "asc");
3074
3072
  if (currentEntity.deleteRootId) {
3075
3073
  query.where({ deleteRootType: currentEntity.deleteRootType, deleteRootId: currentEntity.deleteRootId });
3076
3074
  } else {
3077
3075
  query.where({ deletedAt: currentEntity.deletedAt });
3078
3076
  }
3079
3077
  const descendantsToRestore = await query;
3080
- applyPermissions(ctx, descendantModel.name, descendantModel.name, query, "RESTORE");
3078
+ applyPermissions(ctx2, descendantModel.name, descendantModel.name, query, "RESTORE");
3081
3079
  const restorableDescendants = await query;
3082
3080
  const notRestorableDescendants = descendantsToRestore.filter(
3083
3081
  (descendant) => !restorableDescendants.some((d) => d.id === descendant.id)
@@ -3096,7 +3094,7 @@ var restoreEntity = async (modelName, id, ctx, trigger = "direct-call") => {
3096
3094
  for (const callback of [...beforeHooks, ...mutations, ...afterHooks]) {
3097
3095
  await callback();
3098
3096
  }
3099
- };
3097
+ });
3100
3098
  var createRevision = async (model, data, ctx) => {
3101
3099
  if (model.updatable) {
3102
3100
  const revisionId = (0, import_uuid.v4)();
@@ -3,13 +3,13 @@ import { Context } from '../context';
3
3
  import { EntityModel } from '../models/models';
4
4
  import { Entity, MutationContext, Trigger } from '../models/mutation-hook';
5
5
  export declare const mutationResolver: (_parent: any, args: any, partialCtx: Context, info: GraphQLResolveInfo) => Promise<any>;
6
- export declare const createEntity: (modelName: string, input: Entity, ctx: MutationContext, trigger?: Trigger) => Promise<string>;
7
- export declare const updateEntities: (modelName: string, where: Record<string, unknown>, updateFields: Entity, ctx: MutationContext) => Promise<void>;
8
- export declare const updateEntity: (modelName: string, id: string, input: Entity, ctx: MutationContext, trigger?: Trigger) => Promise<void>;
9
- export declare const deleteEntities: (modelName: string, where: Record<string, unknown>, ctx: MutationContext) => Promise<void>;
6
+ export declare const createEntity: (modelName: string, input: Entity, ctx: MutationContext, trigger?: Trigger) => Promise<any>;
7
+ export declare const updateEntities: (modelName: string, where: Record<string, unknown>, updateFields: Entity, ctx: MutationContext) => Promise<any>;
8
+ export declare const updateEntity: (modelName: string, id: string, input: Entity, ctx: MutationContext, trigger?: Trigger) => Promise<any>;
9
+ export declare const deleteEntities: (modelName: string, where: Record<string, unknown>, ctx: MutationContext) => Promise<any>;
10
10
  export declare const deleteEntity: (modelName: string, id: string, ctx: MutationContext, { dryRun, trigger, }?: {
11
11
  dryRun?: boolean;
12
12
  trigger?: Trigger;
13
- }) => Promise<void>;
14
- export declare const restoreEntity: (modelName: string, id: string, ctx: MutationContext, trigger?: Trigger) => Promise<void>;
13
+ }) => Promise<any>;
14
+ export declare const restoreEntity: (modelName: string, id: string, ctx: MutationContext, trigger?: Trigger) => Promise<any>;
15
15
  export declare const createRevision: (model: EntityModel, data: Entity, ctx: MutationContext) => Promise<void>;
@@ -5,37 +5,35 @@ import { applyPermissions, checkCanWrite, getEntityToMutate } from '../permissio
5
5
  import { anyDateToLuxon } from '../utils';
6
6
  import { resolve } from './resolver';
7
7
  import { AliasGenerator, fetchDisplay, getTechnicalDisplay } from './utils';
8
- export const mutationResolver = async (_parent, args, partialCtx, info) => {
9
- return await partialCtx.knex.transaction(async (knex) => {
10
- const [, mutation, modelName] = it(info.fieldName.match(/^(create|update|delete|restore)(.+)$/));
11
- const ctx = { ...partialCtx, knex, info, aliases: new AliasGenerator() };
12
- switch (mutation) {
13
- case 'create': {
14
- const id = await createEntity(modelName, args.data, ctx, 'mutation');
15
- return await resolve(ctx, id);
16
- }
17
- case 'update': {
18
- const id = args.where.id;
19
- await updateEntity(modelName, id, args.data, ctx, 'mutation');
20
- return await resolve(ctx, id);
21
- }
22
- case 'delete': {
23
- const id = args.where.id;
24
- await deleteEntity(modelName, id, ctx, {
25
- dryRun: args.dryRun,
26
- trigger: 'mutation',
27
- });
28
- return id;
29
- }
30
- case 'restore': {
31
- const id = args.where.id;
32
- await restoreEntity(modelName, id, ctx, 'mutation');
33
- return id;
34
- }
8
+ const withTransaction = async (ctx, fn) => await ctx.knex.transaction(async (knex) => fn({ ...ctx, knex }));
9
+ export const mutationResolver = async (_parent, args, partialCtx, info) => withTransaction({ ...partialCtx, info, aliases: new AliasGenerator() }, async (ctx) => {
10
+ const [, mutation, modelName] = it(info.fieldName.match(/^(create|update|delete|restore)(.+)$/));
11
+ switch (mutation) {
12
+ case 'create': {
13
+ const id = await createEntity(modelName, args.data, ctx, 'mutation');
14
+ return await resolve(ctx, id);
35
15
  }
36
- });
37
- };
38
- export const createEntity = async (modelName, input, ctx, trigger = 'direct-call') => {
16
+ case 'update': {
17
+ const id = args.where.id;
18
+ await updateEntity(modelName, id, args.data, ctx, 'mutation');
19
+ return await resolve(ctx, id);
20
+ }
21
+ case 'delete': {
22
+ const id = args.where.id;
23
+ await deleteEntity(modelName, id, ctx, {
24
+ dryRun: args.dryRun,
25
+ trigger: 'mutation',
26
+ });
27
+ return id;
28
+ }
29
+ case 'restore': {
30
+ const id = args.where.id;
31
+ await restoreEntity(modelName, id, ctx, 'mutation');
32
+ return id;
33
+ }
34
+ }
35
+ });
36
+ export const createEntity = async (modelName, input, ctx, trigger = 'direct-call') => withTransaction(ctx, async (ctx) => {
39
37
  const model = ctx.models.getModel(modelName, 'entity');
40
38
  const normalizedInput = { ...input };
41
39
  if (!normalizedInput.id) {
@@ -92,14 +90,14 @@ export const createEntity = async (modelName, input, ctx, trigger = 'direct-call
92
90
  ctx,
93
91
  });
94
92
  return normalizedInput.id;
95
- };
96
- export const updateEntities = async (modelName, where, updateFields, ctx) => {
93
+ });
94
+ export const updateEntities = async (modelName, where, updateFields, ctx) => withTransaction(ctx, async (ctx) => {
97
95
  const entities = await ctx.knex(modelName).where(where).select('id');
98
96
  for (const entity of entities) {
99
97
  await updateEntity(modelName, entity.id, updateFields, ctx);
100
98
  }
101
- };
102
- export const updateEntity = async (modelName, id, input, ctx, trigger = 'direct-call') => {
99
+ });
100
+ export const updateEntity = async (modelName, id, input, ctx, trigger = 'direct-call') => withTransaction(ctx, async (ctx) => {
103
101
  const model = ctx.models.getModel(modelName, 'entity');
104
102
  const normalizedInput = { ...input };
105
103
  sanitize(ctx, model, normalizedInput);
@@ -131,16 +129,16 @@ export const updateEntity = async (modelName, id, input, ctx, trigger = 'direct-
131
129
  ctx,
132
130
  });
133
131
  }
134
- };
135
- export const deleteEntities = async (modelName, where, ctx) => {
132
+ });
133
+ export const deleteEntities = async (modelName, where, ctx) => withTransaction(ctx, async (ctx) => {
136
134
  const entities = await ctx.knex(modelName).where(where).select('id');
137
135
  for (const entity of entities) {
138
136
  await deleteEntity(modelName, entity.id, ctx, {
139
137
  trigger: 'direct-call',
140
138
  });
141
139
  }
142
- };
143
- export const deleteEntity = async (modelName, id, ctx, { dryRun = false, trigger = 'direct-call', } = {}) => {
140
+ });
141
+ export const deleteEntity = async (modelName, id, ctx, { dryRun = false, trigger = 'direct-call', } = {}) => withTransaction(ctx, async (ctx) => {
144
142
  const model = ctx.models.getModel(modelName, 'entity');
145
143
  const rootModel = model.rootModel;
146
144
  const entity = await getEntityToMutate(ctx, rootModel, { id }, 'DELETE');
@@ -199,7 +197,11 @@ export const deleteEntity = async (modelName, id, ctx, { dryRun = false, trigger
199
197
  }
200
198
  }
201
199
  for (const { targetModel: descendantModel, field: { name, foreignKey, onDelete }, } of currentModel.reverseRelations.filter((reverseRelation) => !reverseRelation.field.inherited)) {
202
- const query = ctx.knex(descendantModel.name).where({ [foreignKey]: currentEntity.id, deleted: false });
200
+ const query = ctx
201
+ .knex(descendantModel.name)
202
+ .where({ [foreignKey]: currentEntity.id, deleted: false })
203
+ .orderBy('createdAt', 'asc')
204
+ .orderBy('id', 'asc');
203
205
  const descendants = await query;
204
206
  if (descendants.length) {
205
207
  switch (onDelete) {
@@ -300,8 +302,8 @@ export const deleteEntity = async (modelName, id, ctx, { dryRun = false, trigger
300
302
  restricted,
301
303
  });
302
304
  }
303
- };
304
- export const restoreEntity = async (modelName, id, ctx, trigger = 'direct-call') => {
305
+ });
306
+ export const restoreEntity = async (modelName, id, ctx, trigger = 'direct-call') => withTransaction(ctx, async (ctx) => {
305
307
  const model = ctx.models.getModel(modelName, 'entity');
306
308
  const rootModel = model.rootModel;
307
309
  const entity = await getEntityToMutate(ctx, rootModel, { id }, 'RESTORE');
@@ -381,7 +383,11 @@ export const restoreEntity = async (modelName, id, ctx, trigger = 'direct-call')
381
383
  for (const { targetModel: descendantModel, field: { foreignKey }, } of currentModel.reverseRelations
382
384
  .filter((reverseRelation) => !reverseRelation.field.inherited)
383
385
  .filter(({ targetModel: { deletable } }) => deletable)) {
384
- const query = ctx.knex(descendantModel.name).where({ [foreignKey]: currentEntity.id, deleted: true });
386
+ const query = ctx
387
+ .knex(descendantModel.name)
388
+ .where({ [foreignKey]: currentEntity.id, deleted: true })
389
+ .orderBy('createdAt', 'asc')
390
+ .orderBy('id', 'asc');
385
391
  if (currentEntity.deleteRootId) {
386
392
  query.where({ deleteRootType: currentEntity.deleteRootType, deleteRootId: currentEntity.deleteRootId });
387
393
  }
@@ -405,7 +411,7 @@ export const restoreEntity = async (modelName, id, ctx, trigger = 'direct-call')
405
411
  for (const callback of [...beforeHooks, ...mutations, ...afterHooks]) {
406
412
  await callback();
407
413
  }
408
- };
414
+ });
409
415
  export const createRevision = async (model, data, ctx) => {
410
416
  if (model.updatable) {
411
417
  const revisionId = uuid();