@vantreeseba/drizzle-graphql 7.7.0 → 7.8.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/dist/index.js CHANGED
@@ -1106,7 +1106,7 @@ var runOnSharedTx = async (state, body) => {
1106
1106
  throw e;
1107
1107
  }
1108
1108
  };
1109
- var runMutation = async (db, context, info, txCtx, body) => {
1109
+ var runMutation = async (db, context, info, txCtx, body, forceTx) => {
1110
1110
  const contextIsObject = context !== null && (typeof context === "object" || typeof context === "function");
1111
1111
  const stateKey = contextIsObject ? context : info.operation;
1112
1112
  let state = txCtx?.states.get(stateKey);
@@ -1115,7 +1115,7 @@ var runMutation = async (db, context, info, txCtx, body) => {
1115
1115
  return body(context[drizzleExecutorKey]);
1116
1116
  }
1117
1117
  if (!txCtx || info.operation.operation !== "mutation") {
1118
- return body(db);
1118
+ return forceTx ? db.transaction((tx) => body(tx)) : body(db);
1119
1119
  }
1120
1120
  const keys = /* @__PURE__ */ new Set();
1121
1121
  const allKnown = collectRootMutationKeys(
@@ -1130,10 +1130,28 @@ var runMutation = async (db, context, info, txCtx, body) => {
1130
1130
  txCtx.states.set(stateKey, state);
1131
1131
  }
1132
1132
  if (state.kind === "passthrough") {
1133
- return body(resolveExecutor(db, context));
1133
+ const executor = resolveExecutor(db, context);
1134
+ return forceTx && executor === db ? db.transaction((tx) => body(tx)) : body(executor);
1134
1135
  }
1135
1136
  return runOnSharedTx(state, body);
1136
1137
  };
1138
+ var normalizeWriteHooks = (hooks) => {
1139
+ if (!hooks) {
1140
+ return void 0;
1141
+ }
1142
+ if (typeof hooks === "function") {
1143
+ return { after: hooks };
1144
+ }
1145
+ const resolved = { before: hooks.before, after: hooks.after };
1146
+ return resolved.before || resolved.after ? resolved : void 0;
1147
+ };
1148
+ var runWriteHook = (hooks, position, payload) => {
1149
+ const hook = position === "before" ? hooks?.before : hooks?.after;
1150
+ if (!hook) {
1151
+ return void 0;
1152
+ }
1153
+ return Promise.resolve(hook({ ...payload, position, rows: payload.rows ?? [] }));
1154
+ };
1137
1155
  var batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, orderCtx, whereArgs) => {
1138
1156
  const cols = getColumns2(targetTable);
1139
1157
  const orderExprs = [
@@ -2782,10 +2800,11 @@ var withScope = (scope, tableName, table, condition, mode) => {
2782
2800
  const predicate = scope?.on(tableName, table, mode);
2783
2801
  return predicate ? and(condition, predicate) : condition;
2784
2802
  };
2785
- var bindPolicies = (policies, filterCtx) => policies?.scope || policies?.contextValues || policies?.softDelete ? {
2803
+ var bindPolicies = (policies, filterCtx) => policies?.scope || policies?.contextValues || policies?.softDelete || policies?.onWrite ? {
2786
2804
  scope: policies.scope || policies.softDelete ? (context) => resolveScope(policies, context, filterCtx) : void 0,
2787
2805
  contextValues: policies.contextValues,
2788
- softDelete: policies.softDelete
2806
+ softDelete: policies.softDelete,
2807
+ onWrite: policies.onWrite
2789
2808
  } : void 0;
2790
2809
  var applyContextValues = (values, hooks, context) => {
2791
2810
  if (!hooks) {
@@ -4374,22 +4393,49 @@ var generateInsertArray = (db, tableName, table, baseType, fieldName, txCtx, pol
4374
4393
  type: new GraphQLNonNull5(new GraphQLList5(new GraphQLNonNull5(baseType)))
4375
4394
  }
4376
4395
  };
4396
+ const hooks = policies?.onWrite?.(tableName, "insert");
4377
4397
  return {
4378
4398
  name: fieldName,
4379
4399
  resolver: async (_source, args, context, info) => {
4380
4400
  try {
4381
- return await runMutation(db, context, info, txCtx, async (executor) => {
4382
- const input = applyContextValuesAll(
4383
- remapFromGraphQLArrayInput(args.values, table),
4384
- policies?.contextValues?.(tableName),
4385
- context
4386
- );
4387
- if (!input.length) {
4388
- throw new GraphQLError6("No values were provided!");
4389
- }
4390
- await executor.insert(table).values(input);
4391
- return { isSuccess: true };
4392
- });
4401
+ return await runMutation(
4402
+ db,
4403
+ context,
4404
+ info,
4405
+ txCtx,
4406
+ async (executor) => {
4407
+ const input = applyContextValuesAll(
4408
+ remapFromGraphQLArrayInput(args.values, table),
4409
+ policies?.contextValues?.(tableName),
4410
+ context
4411
+ );
4412
+ if (!input.length) {
4413
+ throw new GraphQLError6("No values were provided!");
4414
+ }
4415
+ await runWriteHook(hooks, "before", {
4416
+ table: tableName,
4417
+ operation: "insert",
4418
+ single: false,
4419
+ args,
4420
+ context,
4421
+ info,
4422
+ tx: executor
4423
+ });
4424
+ await executor.insert(table).values(input);
4425
+ await runWriteHook(hooks, "after", {
4426
+ table: tableName,
4427
+ operation: "insert",
4428
+ single: false,
4429
+ args,
4430
+ rows: [],
4431
+ context,
4432
+ info,
4433
+ tx: executor
4434
+ });
4435
+ return { isSuccess: true };
4436
+ },
4437
+ !!hooks
4438
+ );
4393
4439
  } catch (e) {
4394
4440
  throw toGraphQLError(e);
4395
4441
  }
@@ -4403,19 +4449,46 @@ var generateInsertSingle = (db, tableName, table, baseType, fieldName, txCtx, po
4403
4449
  type: new GraphQLNonNull5(baseType)
4404
4450
  }
4405
4451
  };
4452
+ const hooks = policies?.onWrite?.(tableName, "insert");
4406
4453
  return {
4407
4454
  name: fieldName,
4408
4455
  resolver: async (_source, args, context, info) => {
4409
4456
  try {
4410
- return await runMutation(db, context, info, txCtx, async (executor) => {
4411
- const input = applyContextValues(
4412
- remapFromGraphQLSingleInput(args.values, table),
4413
- policies?.contextValues?.(tableName),
4414
- context
4415
- );
4416
- await executor.insert(table).values(input);
4417
- return { isSuccess: true };
4418
- });
4457
+ return await runMutation(
4458
+ db,
4459
+ context,
4460
+ info,
4461
+ txCtx,
4462
+ async (executor) => {
4463
+ await runWriteHook(hooks, "before", {
4464
+ table: tableName,
4465
+ operation: "insert",
4466
+ single: true,
4467
+ args,
4468
+ context,
4469
+ info,
4470
+ tx: executor
4471
+ });
4472
+ const input = applyContextValues(
4473
+ remapFromGraphQLSingleInput(args.values, table),
4474
+ policies?.contextValues?.(tableName),
4475
+ context
4476
+ );
4477
+ await executor.insert(table).values(input);
4478
+ await runWriteHook(hooks, "after", {
4479
+ table: tableName,
4480
+ operation: "insert",
4481
+ single: true,
4482
+ args,
4483
+ rows: [],
4484
+ context,
4485
+ info,
4486
+ tx: executor
4487
+ });
4488
+ return { isSuccess: true };
4489
+ },
4490
+ !!hooks
4491
+ );
4419
4492
  } catch (e) {
4420
4493
  throw toGraphQLError(e);
4421
4494
  }
@@ -4434,35 +4507,62 @@ var generateUpsert = (db, tableName, table, baseType, onConflictType, fieldName,
4434
4507
  }
4435
4508
  };
4436
4509
  const pkNames = mysqlPrimaryKeyPropNames(table);
4510
+ const hooks = policies?.onWrite?.(tableName, "upsert");
4437
4511
  return {
4438
4512
  name: fieldName,
4439
4513
  resolver: async (_source, args, context, info) => {
4440
4514
  try {
4441
- return await runMutation(db, context, info, txCtx, async (executor) => {
4442
- const input = applyContextValuesAll(
4443
- single ? [remapFromGraphQLSingleInput(args.values, table)] : remapFromGraphQLArrayInput(args.values, table),
4444
- policies?.contextValues?.(tableName),
4445
- context
4446
- );
4447
- if (!input.length) {
4448
- throw new GraphQLError6("No values were provided!");
4449
- }
4450
- const plan = resolveConflictPlan({
4451
- table,
4452
- values: input,
4453
- onConflict: args.onConflict,
4454
- pkNames,
4455
- uniqueSets: [],
4456
- excludedRef: mysqlValuesColumnRef,
4457
- withTarget: false
4458
- });
4459
- if (plan.action === "NOTHING") {
4460
- await executor.insert(table).ignore().values(input);
4461
- } else {
4462
- await executor.insert(table).values(input).onDuplicateKeyUpdate({ set: plan.set });
4463
- }
4464
- return { isSuccess: true };
4465
- });
4515
+ return await runMutation(
4516
+ db,
4517
+ context,
4518
+ info,
4519
+ txCtx,
4520
+ async (executor) => {
4521
+ const input = applyContextValuesAll(
4522
+ single ? [remapFromGraphQLSingleInput(args.values, table)] : remapFromGraphQLArrayInput(args.values, table),
4523
+ policies?.contextValues?.(tableName),
4524
+ context
4525
+ );
4526
+ if (!input.length) {
4527
+ throw new GraphQLError6("No values were provided!");
4528
+ }
4529
+ await runWriteHook(hooks, "before", {
4530
+ table: tableName,
4531
+ operation: "upsert",
4532
+ single,
4533
+ args,
4534
+ context,
4535
+ info,
4536
+ tx: executor
4537
+ });
4538
+ const plan = resolveConflictPlan({
4539
+ table,
4540
+ values: input,
4541
+ onConflict: args.onConflict,
4542
+ pkNames,
4543
+ uniqueSets: [],
4544
+ excludedRef: mysqlValuesColumnRef,
4545
+ withTarget: false
4546
+ });
4547
+ if (plan.action === "NOTHING") {
4548
+ await executor.insert(table).ignore().values(input);
4549
+ } else {
4550
+ await executor.insert(table).values(input).onDuplicateKeyUpdate({ set: plan.set });
4551
+ }
4552
+ await runWriteHook(hooks, "after", {
4553
+ table: tableName,
4554
+ operation: "upsert",
4555
+ single,
4556
+ args,
4557
+ rows: [],
4558
+ context,
4559
+ info,
4560
+ tx: executor
4561
+ });
4562
+ return { isSuccess: true };
4563
+ },
4564
+ !!hooks
4565
+ );
4466
4566
  } catch (e) {
4467
4567
  throw toGraphQLError(e);
4468
4568
  }
@@ -4479,37 +4579,64 @@ var generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, sing
4479
4579
  type: single || requireWhere ? new GraphQLNonNull5(filterArgs) : filterArgs
4480
4580
  }
4481
4581
  };
4582
+ const hooks = policies?.onWrite?.(tableName, "update");
4482
4583
  return {
4483
4584
  name: fieldName,
4484
4585
  resolver: async (_source, args, context, info) => {
4485
4586
  try {
4486
- return await runMutation(db, context, info, txCtx, async (executor) => {
4487
- const { where, set } = args;
4488
- const scope = policies?.scope?.(context);
4489
- const input = stripContextValues(
4490
- remapUpdateInput(set, table, tableName),
4491
- policies?.contextValues?.(tableName)
4492
- );
4493
- if (!Object.keys(input).length) {
4494
- throw new GraphQLError6("Unable to update with no values specified!");
4495
- }
4496
- const relationCtx = relationFilterCtx(filterCtx, tableName);
4497
- const filters = withScope(
4498
- scope,
4499
- tableName,
4500
- table,
4501
- single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0
4502
- );
4503
- if (single) {
4504
- await assertSingleMatch(executor, table, filters, fieldName);
4505
- }
4506
- let query = executor.update(table).set(input);
4507
- if (filters) {
4508
- query = query.where(filters);
4509
- }
4510
- await query;
4511
- return { isSuccess: true };
4512
- });
4587
+ return await runMutation(
4588
+ db,
4589
+ context,
4590
+ info,
4591
+ txCtx,
4592
+ async (executor) => {
4593
+ const { where, set } = args;
4594
+ const scope = policies?.scope?.(context);
4595
+ const input = stripContextValues(
4596
+ remapUpdateInput(set, table, tableName),
4597
+ policies?.contextValues?.(tableName)
4598
+ );
4599
+ if (!Object.keys(input).length) {
4600
+ throw new GraphQLError6("Unable to update with no values specified!");
4601
+ }
4602
+ await runWriteHook(hooks, "before", {
4603
+ table: tableName,
4604
+ operation: "update",
4605
+ single,
4606
+ args,
4607
+ context,
4608
+ info,
4609
+ tx: executor
4610
+ });
4611
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
4612
+ const filters = withScope(
4613
+ scope,
4614
+ tableName,
4615
+ table,
4616
+ single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0
4617
+ );
4618
+ if (single) {
4619
+ await assertSingleMatch(executor, table, filters, fieldName);
4620
+ }
4621
+ let query = executor.update(table).set(input);
4622
+ if (filters) {
4623
+ query = query.where(filters);
4624
+ }
4625
+ await query;
4626
+ await runWriteHook(hooks, "after", {
4627
+ table: tableName,
4628
+ operation: "update",
4629
+ single,
4630
+ args,
4631
+ rows: [],
4632
+ context,
4633
+ info,
4634
+ tx: executor
4635
+ });
4636
+ return { isSuccess: true };
4637
+ },
4638
+ !!hooks
4639
+ );
4513
4640
  } catch (e) {
4514
4641
  throw toGraphQLError(e);
4515
4642
  }
@@ -4523,43 +4650,70 @@ var generateUpdateMany = (db, tableName, table, updateManyInput, fieldName, filt
4523
4650
  type: new GraphQLNonNull5(new GraphQLList5(new GraphQLNonNull5(updateManyInput)))
4524
4651
  }
4525
4652
  };
4653
+ const hooks = policies?.onWrite?.(tableName, "updateMany");
4526
4654
  return {
4527
4655
  name: fieldName,
4528
4656
  resolver: async (_source, args, context, info) => {
4529
4657
  try {
4530
- return await runMutation(db, context, info, txCtx, async (executor) => {
4531
- const { updates } = args;
4532
- if (!updates.length) {
4533
- throw new GraphQLError6("No updates were provided!");
4534
- }
4535
- const scope = policies?.scope?.(context);
4536
- const contextColumns = policies?.contextValues?.(tableName);
4537
- const entries = updates.map(({ where, set }) => {
4538
- const input = stripContextValues(remapUpdateInput(set, table, tableName), contextColumns);
4539
- if (!Object.keys(input).length) {
4540
- throw new GraphQLError6("Unable to update with no values specified!");
4658
+ return await runMutation(
4659
+ db,
4660
+ context,
4661
+ info,
4662
+ txCtx,
4663
+ async (executor) => {
4664
+ const { updates } = args;
4665
+ if (!updates.length) {
4666
+ throw new GraphQLError6("No updates were provided!");
4541
4667
  }
4542
- return {
4543
- set: input,
4544
- filters: withScope(
4545
- scope,
4546
- tableName,
4547
- table,
4548
- where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0
4549
- )
4550
- };
4551
- });
4552
- await executor.transaction(async (tx) => {
4553
- for (const entry of entries) {
4554
- let query = tx.update(table).set(entry.set);
4555
- if (entry.filters) {
4556
- query = query.where(entry.filters);
4668
+ await runWriteHook(hooks, "before", {
4669
+ table: tableName,
4670
+ operation: "updateMany",
4671
+ single: false,
4672
+ args,
4673
+ context,
4674
+ info,
4675
+ tx: executor
4676
+ });
4677
+ const scope = policies?.scope?.(context);
4678
+ const contextColumns = policies?.contextValues?.(tableName);
4679
+ const entries = updates.map(({ where, set }) => {
4680
+ const input = stripContextValues(remapUpdateInput(set, table, tableName), contextColumns);
4681
+ if (!Object.keys(input).length) {
4682
+ throw new GraphQLError6("Unable to update with no values specified!");
4557
4683
  }
4558
- await query;
4559
- }
4560
- });
4561
- return { isSuccess: true };
4562
- });
4684
+ return {
4685
+ set: input,
4686
+ filters: withScope(
4687
+ scope,
4688
+ tableName,
4689
+ table,
4690
+ where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0
4691
+ )
4692
+ };
4693
+ });
4694
+ await executor.transaction(async (tx) => {
4695
+ for (const entry of entries) {
4696
+ let query = tx.update(table).set(entry.set);
4697
+ if (entry.filters) {
4698
+ query = query.where(entry.filters);
4699
+ }
4700
+ await query;
4701
+ }
4702
+ });
4703
+ await runWriteHook(hooks, "after", {
4704
+ table: tableName,
4705
+ operation: "updateMany",
4706
+ single: false,
4707
+ args,
4708
+ rows: [],
4709
+ context,
4710
+ info,
4711
+ tx: executor
4712
+ });
4713
+ return { isSuccess: true };
4714
+ },
4715
+ !!hooks
4716
+ );
4563
4717
  } catch (e) {
4564
4718
  throw toGraphQLError(e);
4565
4719
  }
@@ -4569,36 +4723,64 @@ var generateUpdateMany = (db, tableName, table, updateManyInput, fieldName, filt
4569
4723
  };
4570
4724
  var generateDelete = (db, tableName, table, filterArgs, fieldName, single, requireWhere, filterCtx, txCtx, policies, restore = false) => {
4571
4725
  const softDelete = policies?.softDelete?.(tableName);
4726
+ const operation = restore ? "restore" : "delete";
4572
4727
  const queryArgs = {
4573
4728
  where: {
4574
4729
  type: single || requireWhere ? new GraphQLNonNull5(filterArgs) : filterArgs
4575
4730
  }
4576
4731
  };
4732
+ const hooks = policies?.onWrite?.(tableName, operation);
4577
4733
  return {
4578
4734
  name: fieldName,
4579
4735
  resolver: async (_source, args, context, info) => {
4580
4736
  try {
4581
- return await runMutation(db, context, info, txCtx, async (executor) => {
4582
- const { where } = args;
4583
- const scope = policies?.scope?.(context);
4584
- const relationCtx = relationFilterCtx(filterCtx, tableName);
4585
- const filters = withScope(
4586
- scope,
4587
- tableName,
4588
- table,
4589
- single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0,
4590
- restore ? "ONLY" : void 0
4591
- );
4592
- if (single) {
4593
- await assertSingleMatch(executor, table, filters, fieldName);
4594
- }
4595
- let query = softDelete ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
4596
- if (filters) {
4597
- query = query.where(filters);
4598
- }
4599
- await query;
4600
- return { isSuccess: true };
4601
- });
4737
+ return await runMutation(
4738
+ db,
4739
+ context,
4740
+ info,
4741
+ txCtx,
4742
+ async (executor) => {
4743
+ await runWriteHook(hooks, "before", {
4744
+ table: tableName,
4745
+ operation,
4746
+ single,
4747
+ args,
4748
+ context,
4749
+ info,
4750
+ tx: executor
4751
+ });
4752
+ const { where } = args;
4753
+ const scope = policies?.scope?.(context);
4754
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
4755
+ const filters = withScope(
4756
+ scope,
4757
+ tableName,
4758
+ table,
4759
+ single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0,
4760
+ restore ? "ONLY" : void 0
4761
+ );
4762
+ if (single) {
4763
+ await assertSingleMatch(executor, table, filters, fieldName);
4764
+ }
4765
+ let query = softDelete ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
4766
+ if (filters) {
4767
+ query = query.where(filters);
4768
+ }
4769
+ await query;
4770
+ await runWriteHook(hooks, "after", {
4771
+ table: tableName,
4772
+ operation,
4773
+ single,
4774
+ args,
4775
+ rows: [],
4776
+ context,
4777
+ info,
4778
+ tx: executor
4779
+ });
4780
+ return { isSuccess: true };
4781
+ },
4782
+ !!hooks
4783
+ );
4602
4784
  } catch (e) {
4603
4785
  throw toGraphQLError(e);
4604
4786
  }
@@ -5074,7 +5256,7 @@ import {
5074
5256
  import { parseResolveInfo as parseResolveInfo3 } from "graphql-parse-resolve-info";
5075
5257
 
5076
5258
  // src/util/builders/nested-writes.ts
5077
- import { and as and3, eq as eq3, getColumns as getColumns4, is as is5, One as One4 } from "drizzle-orm";
5259
+ import { and as and3, Column, eq as eq3, getColumns as getColumns4, inArray as inArray3, is as is5, One as One4 } from "drizzle-orm";
5078
5260
  import {
5079
5261
  GraphQLBoolean as GraphQLBoolean4,
5080
5262
  GraphQLError as GraphQLError7,
@@ -5083,6 +5265,64 @@ import {
5083
5265
  GraphQLNonNull as GraphQLNonNull6
5084
5266
  } from "graphql";
5085
5267
  var isOptionalOnInsert = (column) => !column.notNull || !!column.hasDefault || !!column.defaultFn;
5268
+ var propNameOf = (table, column) => {
5269
+ if (!column) {
5270
+ return void 0;
5271
+ }
5272
+ for (const [propName, candidate] of Object.entries(getColumns4(table))) {
5273
+ if (candidate === column) {
5274
+ return propName;
5275
+ }
5276
+ }
5277
+ return void 0;
5278
+ };
5279
+ var throughColumn = (entry) => entry && is5(entry, Column) ? entry : entry?._?.column;
5280
+ var buildThroughPlan = (relationName, relation, table, targetTableName, targetTable) => {
5281
+ if (is5(relation, One4)) {
5282
+ return void 0;
5283
+ }
5284
+ const junction = relation.throughTable;
5285
+ const sourceEntries = relation.through?.source;
5286
+ const targetEntries = relation.through?.target;
5287
+ if (!junction || sourceEntries?.length !== 1 || targetEntries?.length !== 1) {
5288
+ return void 0;
5289
+ }
5290
+ if (relation.sourceColumns?.length !== 1 || relation.targetColumns?.length !== 1) {
5291
+ return void 0;
5292
+ }
5293
+ const localCol = relation.sourceColumns[0];
5294
+ const foreignCol = relation.targetColumns[0];
5295
+ const localColPropName = propNameOf(table, localCol);
5296
+ const foreignColPropName = propNameOf(targetTable, foreignCol);
5297
+ const sourceColPropName = propNameOf(junction, throughColumn(sourceEntries[0]));
5298
+ const targetColPropName = propNameOf(junction, throughColumn(targetEntries[0]));
5299
+ if (!localColPropName || !foreignColPropName || !sourceColPropName || !targetColPropName) {
5300
+ return void 0;
5301
+ }
5302
+ const junctionColumns = getColumns4(junction);
5303
+ const sourceCol = junctionColumns[sourceColPropName];
5304
+ const targetCol = junctionColumns[targetColPropName];
5305
+ const unsuppliable = Object.entries(junctionColumns).some(
5306
+ ([propName, column]) => propName !== sourceColPropName && propName !== targetColPropName && !isOptionalOnInsert(column)
5307
+ );
5308
+ if (unsuppliable) {
5309
+ return void 0;
5310
+ }
5311
+ return {
5312
+ relationName,
5313
+ targetTableName,
5314
+ targetTable,
5315
+ localColPropName,
5316
+ foreignColPropName,
5317
+ foreignCol,
5318
+ localCol,
5319
+ isOne: false,
5320
+ // The link is written after the parent row exists, exactly like a child-side foreign key.
5321
+ fkSide: "child",
5322
+ canDetach: true,
5323
+ through: { table: junction, sourceColPropName, targetColPropName, sourceCol, targetCol }
5324
+ };
5325
+ };
5086
5326
  var buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns) => {
5087
5327
  const uniqueCache = /* @__PURE__ */ new Map();
5088
5328
  const singleColumnUniques = (tableName, table) => {
@@ -5112,14 +5352,18 @@ var buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrim
5112
5352
  const tablePlans = {};
5113
5353
  for (const [relationName, relEntry] of Object.entries(relations)) {
5114
5354
  const relation = relEntry.relation;
5115
- if (relation.through) {
5116
- continue;
5117
- }
5118
5355
  const targetTableName = relEntry.targetTableName;
5119
5356
  const targetTable = tables[targetTableName];
5120
5357
  if (!targetTable) {
5121
5358
  continue;
5122
5359
  }
5360
+ if (relation.through) {
5361
+ const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable);
5362
+ if (throughPlan) {
5363
+ tablePlans[relationName] = throughPlan;
5364
+ }
5365
+ continue;
5366
+ }
5123
5367
  const joinColumns = extractJoinColumns(relEntry, table, targetTable);
5124
5368
  if (!joinColumns) {
5125
5369
  continue;
@@ -5204,11 +5448,31 @@ var createNestedWriteTypes = (params) => {
5204
5448
  return cached;
5205
5449
  }
5206
5450
  const filters = targetFilters(plan);
5207
- const payload = payloadType(tableName, typeName, plan);
5208
5451
  const targetTypeName = resolveTypeName(plan.targetTableName, typeNameMapper);
5209
5452
  const fields = {};
5210
- if (plan.isOne) {
5211
- fields["create"] = { type: payload, description: `Insert a ${targetTypeName} row and attach it` };
5453
+ if (plan.through) {
5454
+ if (!filters) {
5455
+ return void 0;
5456
+ }
5457
+ fields["connect"] = {
5458
+ type: listOf(filters),
5459
+ description: `Link every existing ${targetTypeName} row these match. Already-linked rows are left alone.`
5460
+ };
5461
+ if (forUpdate) {
5462
+ fields["disconnect"] = {
5463
+ type: listOf(filters),
5464
+ description: `Unlink every linked ${targetTypeName} row these match, leaving the rows in place`
5465
+ };
5466
+ fields["set"] = {
5467
+ type: listOf(filters),
5468
+ description: `Replace the whole set: unlink everything linked, then link every ${targetTypeName} row these match. \`set: []\` clears the relation. Applied before \`disconnect\` and \`connect\`.`
5469
+ };
5470
+ }
5471
+ } else if (plan.isOne) {
5472
+ fields["create"] = {
5473
+ type: payloadType(tableName, typeName, plan),
5474
+ description: `Insert a ${targetTypeName} row and attach it`
5475
+ };
5212
5476
  if (filters) {
5213
5477
  fields["connect"] = {
5214
5478
  type: filters,
@@ -5222,7 +5486,10 @@ var createNestedWriteTypes = (params) => {
5222
5486
  };
5223
5487
  }
5224
5488
  } else {
5225
- fields["create"] = { type: listOf(payload), description: `Insert these ${targetTypeName} rows and attach them` };
5489
+ fields["create"] = {
5490
+ type: listOf(payloadType(tableName, typeName, plan)),
5491
+ description: `Insert these ${targetTypeName} rows and attach them`
5492
+ };
5226
5493
  if (filters) {
5227
5494
  fields["connect"] = {
5228
5495
  type: listOf(filters),
@@ -5304,6 +5571,48 @@ var createNestedWriteRuntime = (params) => {
5304
5571
  );
5305
5572
  }
5306
5573
  };
5574
+ const selectedTargetKeys = async (executor, plan, filter, operation, scope) => {
5575
+ const rows = await executor.select().from(plan.targetTable).where(conditionOf(plan, filter, operation, scope));
5576
+ return rows.map((row) => row[plan.foreignColPropName]).filter((value) => value !== void 0 && value !== null);
5577
+ };
5578
+ const applyThroughOps = async (executor, plan, key, op, scope) => {
5579
+ const through = plan.through;
5580
+ const unlinkAll = () => executor.delete(through.table).where(eq3(through.sourceCol, key));
5581
+ const link = async (operands, operation) => {
5582
+ const existing = new Set(
5583
+ (await executor.select().from(through.table).where(eq3(through.sourceCol, key))).map(
5584
+ (row) => row[through.targetColPropName]
5585
+ )
5586
+ );
5587
+ const rows = [];
5588
+ for (const filter of operands) {
5589
+ for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) {
5590
+ if (!existing.has(targetKey)) {
5591
+ existing.add(targetKey);
5592
+ rows.push({ [through.sourceColPropName]: key, [through.targetColPropName]: targetKey });
5593
+ }
5594
+ }
5595
+ }
5596
+ if (rows.length) {
5597
+ await executor.insert(through.table).values(rows);
5598
+ }
5599
+ };
5600
+ if (op["set"] !== void 0 && op["set"] !== null) {
5601
+ await unlinkAll();
5602
+ await link(asArray(op["set"]), "set");
5603
+ }
5604
+ if (op["disconnect"] !== void 0 && op["disconnect"] !== null) {
5605
+ for (const filter of asArray(op["disconnect"])) {
5606
+ const targetKeys = await selectedTargetKeys(executor, plan, filter, "disconnect", scope);
5607
+ if (targetKeys.length) {
5608
+ await executor.delete(through.table).where(and3(eq3(through.sourceCol, key), inArray3(through.targetCol, targetKeys)));
5609
+ }
5610
+ }
5611
+ }
5612
+ if (op["connect"] !== void 0 && op["connect"] !== null) {
5613
+ await link(asArray(op["connect"]), "connect");
5614
+ }
5615
+ };
5307
5616
  const detachAll = async (executor, plan, key, scope) => executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(withScope(scope, plan.targetTableName, plan.targetTable, eq3(plan.foreignCol, key)));
5308
5617
  return {
5309
5618
  enabled: (tableName) => !!plans[tableName],
@@ -5400,6 +5709,10 @@ var createNestedWriteRuntime = (params) => {
5400
5709
  `Drizzle-GraphQL Error: cannot write through '${relationName}': the ${tableName} row has no '${plan.localColPropName}' value to attach to.`
5401
5710
  );
5402
5711
  }
5712
+ if (plan.through) {
5713
+ await applyThroughOps(executor, plan, key, op, scope);
5714
+ continue;
5715
+ }
5403
5716
  if (op["set"] !== void 0) {
5404
5717
  await detachAll(executor, plan, key, scope);
5405
5718
  for (const filter of asArray(op["set"])) {
@@ -5679,58 +5992,85 @@ var generateInsertArray2 = (db, tableName, table, tables, relationMap, baseType,
5679
5992
  }
5680
5993
  };
5681
5994
  const pkNames = pgPrimaryKeyPropNames(table);
5995
+ const hooks = policies?.onWrite?.(tableName, "insert");
5682
5996
  return {
5683
5997
  name: fieldName,
5684
5998
  resolver: async (_source, args, context, info) => {
5685
5999
  try {
5686
- return await runMutation(db, context, info, txCtx, async (executor) => {
5687
- if (!args.values.length) {
5688
- throw new GraphQLError8("No values were provided!");
5689
- }
5690
- const entries = nested?.enabled(tableName) ? args.values.map((values) => nested.split(tableName, values)) : void 0;
5691
- const nestedEntries = entries?.some((entry) => nested.hasOps(entry.ops)) ? entries : void 0;
5692
- const contextColumns = policies?.contextValues?.(tableName);
5693
- const scope = policies?.scope?.(context);
5694
- const input = nestedEntries ? [] : applyContextValuesAll(
5695
- remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : args.values, table),
5696
- contextColumns,
5697
- context
5698
- );
5699
- const parsedInfo = parseResolveInfo3(info, {
5700
- deep: true
5701
- });
5702
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
5703
- relationMap,
5704
- tables,
5705
- tableName,
5706
- typeName,
5707
- typeNameMapper,
5708
- table,
5709
- pkNames,
5710
- parsedInfo,
5711
- limits,
5712
- scope
5713
- });
5714
- const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
5715
- const runInsert = async (target, values) => {
5716
- let query = target.insert(table).values(values).returning(returning);
5717
- if (conflictDoNothing) {
5718
- query = query.onConflictDoNothing();
6000
+ return await runMutation(
6001
+ db,
6002
+ context,
6003
+ info,
6004
+ txCtx,
6005
+ async (executor) => {
6006
+ if (!args.values.length) {
6007
+ throw new GraphQLError8("No values were provided!");
5719
6008
  }
5720
- return await query;
5721
- };
5722
- const result = nestedEntries ? await writeWithNestedOps({
5723
- executor,
5724
- runtime: nested,
5725
- tableName,
5726
- entries: nestedEntries,
5727
- remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
5728
- write: (tx, values) => runInsert(tx, [values]),
5729
- context
5730
- }) : await runInsert(executor, input);
5731
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
5732
- return remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
5733
- });
6009
+ await runWriteHook(hooks, "before", {
6010
+ table: tableName,
6011
+ operation: "insert",
6012
+ single: false,
6013
+ args,
6014
+ context,
6015
+ info,
6016
+ tx: executor
6017
+ });
6018
+ const entries = nested?.enabled(tableName) ? args.values.map((values) => nested.split(tableName, values)) : void 0;
6019
+ const nestedEntries = entries?.some((entry) => nested.hasOps(entry.ops)) ? entries : void 0;
6020
+ const contextColumns = policies?.contextValues?.(tableName);
6021
+ const scope = policies?.scope?.(context);
6022
+ const input = nestedEntries ? [] : applyContextValuesAll(
6023
+ remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : args.values, table),
6024
+ contextColumns,
6025
+ context
6026
+ );
6027
+ const parsedInfo = parseResolveInfo3(info, {
6028
+ deep: true
6029
+ });
6030
+ const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6031
+ relationMap,
6032
+ tables,
6033
+ tableName,
6034
+ typeName,
6035
+ typeNameMapper,
6036
+ table,
6037
+ pkNames,
6038
+ parsedInfo,
6039
+ limits,
6040
+ scope
6041
+ });
6042
+ const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
6043
+ const runInsert = async (target, values) => {
6044
+ let query = target.insert(table).values(values).returning(returning);
6045
+ if (conflictDoNothing) {
6046
+ query = query.onConflictDoNothing();
6047
+ }
6048
+ return await query;
6049
+ };
6050
+ const result = nestedEntries ? await writeWithNestedOps({
6051
+ executor,
6052
+ runtime: nested,
6053
+ tableName,
6054
+ entries: nestedEntries,
6055
+ remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
6056
+ write: (tx, values) => runInsert(tx, [values]),
6057
+ context
6058
+ }) : await runInsert(executor, input);
6059
+ await runWriteHook(hooks, "after", {
6060
+ table: tableName,
6061
+ operation: "insert",
6062
+ single: false,
6063
+ args,
6064
+ rows: result,
6065
+ context,
6066
+ info,
6067
+ tx: executor
6068
+ });
6069
+ const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
6070
+ return remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
6071
+ },
6072
+ !!hooks
6073
+ );
5734
6074
  } catch (e) {
5735
6075
  throw toGraphQLError(e);
5736
6076
  }
@@ -5745,61 +6085,88 @@ var generateInsertSingle2 = (db, tableName, table, tables, relationMap, baseType
5745
6085
  }
5746
6086
  };
5747
6087
  const pkNames = pgPrimaryKeyPropNames(table);
6088
+ const hooks = policies?.onWrite?.(tableName, "insert");
5748
6089
  return {
5749
6090
  name: fieldName,
5750
6091
  resolver: async (_source, args, context, info) => {
5751
6092
  try {
5752
- return await runMutation(db, context, info, txCtx, async (executor) => {
5753
- const entry = nested?.enabled(tableName) ? nested.split(tableName, args.values) : void 0;
5754
- const nestedEntry = entry && nested.hasOps(entry.ops) ? entry : void 0;
5755
- const contextColumns = policies?.contextValues?.(tableName);
5756
- const scope = policies?.scope?.(context);
5757
- const input = nestedEntry ? {} : applyContextValues(
5758
- remapFromGraphQLSingleInput(entry ? entry.columns : args.values, table),
5759
- contextColumns,
5760
- context
5761
- );
5762
- const parsedInfo = parseResolveInfo3(info, {
5763
- deep: true
5764
- });
5765
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
5766
- relationMap,
5767
- tables,
5768
- tableName,
5769
- typeName,
5770
- typeNameMapper,
5771
- table,
5772
- pkNames,
5773
- parsedInfo,
5774
- limits,
5775
- scope
5776
- });
5777
- const returning = nestedEntry ? nested.withJoinColumns(tableName, nestedEntry.ops, { ...columns }, table) : columns;
5778
- const runInsert = async (target, values) => {
5779
- let query = target.insert(table).values(values).returning(returning);
5780
- if (conflictDoNothing) {
5781
- query = query.onConflictDoNothing();
5782
- }
5783
- return await query;
5784
- };
5785
- const result = nestedEntry ? await writeWithNestedOps({
5786
- executor,
5787
- runtime: nested,
5788
- tableName,
5789
- entries: [nestedEntry],
5790
- remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
5791
- write: runInsert,
5792
- context
5793
- }) : await runInsert(executor, input);
5794
- if (!result[0]) {
5795
- if (!conflictDoNothing) {
5796
- throw new GraphQLError8(`${fieldName}: the insert returned no row.`);
6093
+ return await runMutation(
6094
+ db,
6095
+ context,
6096
+ info,
6097
+ txCtx,
6098
+ async (executor) => {
6099
+ await runWriteHook(hooks, "before", {
6100
+ table: tableName,
6101
+ operation: "insert",
6102
+ single: true,
6103
+ args,
6104
+ context,
6105
+ info,
6106
+ tx: executor
6107
+ });
6108
+ const entry = nested?.enabled(tableName) ? nested.split(tableName, args.values) : void 0;
6109
+ const nestedEntry = entry && nested.hasOps(entry.ops) ? entry : void 0;
6110
+ const contextColumns = policies?.contextValues?.(tableName);
6111
+ const scope = policies?.scope?.(context);
6112
+ const input = nestedEntry ? {} : applyContextValues(
6113
+ remapFromGraphQLSingleInput(entry ? entry.columns : args.values, table),
6114
+ contextColumns,
6115
+ context
6116
+ );
6117
+ const parsedInfo = parseResolveInfo3(info, {
6118
+ deep: true
6119
+ });
6120
+ const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6121
+ relationMap,
6122
+ tables,
6123
+ tableName,
6124
+ typeName,
6125
+ typeNameMapper,
6126
+ table,
6127
+ pkNames,
6128
+ parsedInfo,
6129
+ limits,
6130
+ scope
6131
+ });
6132
+ const returning = nestedEntry ? nested.withJoinColumns(tableName, nestedEntry.ops, { ...columns }, table) : columns;
6133
+ const runInsert = async (target, values) => {
6134
+ let query = target.insert(table).values(values).returning(returning);
6135
+ if (conflictDoNothing) {
6136
+ query = query.onConflictDoNothing();
6137
+ }
6138
+ return await query;
6139
+ };
6140
+ const result = nestedEntry ? await writeWithNestedOps({
6141
+ executor,
6142
+ runtime: nested,
6143
+ tableName,
6144
+ entries: [nestedEntry],
6145
+ remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
6146
+ write: runInsert,
6147
+ context
6148
+ }) : await runInsert(executor, input);
6149
+ await runWriteHook(hooks, "after", {
6150
+ table: tableName,
6151
+ operation: "insert",
6152
+ single: true,
6153
+ args,
6154
+ rows: result,
6155
+ context,
6156
+ info,
6157
+ tx: executor
6158
+ });
6159
+ if (!result[0]) {
6160
+ if (!conflictDoNothing) {
6161
+ throw new GraphQLError8(`${fieldName}: the insert returned no row.`);
6162
+ }
6163
+ return void 0;
5797
6164
  }
5798
- return void 0;
5799
- }
5800
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
5801
- return remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap);
5802
- });
6165
+ const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
6166
+ return remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap);
6167
+ },
6168
+ !!hooks
6169
+ );
5803
6170
  } catch (e) {
5804
6171
  throw toGraphQLError(e);
5805
6172
  }
@@ -5818,69 +6185,96 @@ var generateUpsert2 = (db, tableName, table, tables, relationMap, baseType, onCo
5818
6185
  }
5819
6186
  };
5820
6187
  const pkNames = pgPrimaryKeyPropNames(table);
6188
+ const hooks = policies?.onWrite?.(tableName, "upsert");
5821
6189
  return {
5822
6190
  name: fieldName,
5823
6191
  resolver: async (_source, args, context, info) => {
5824
6192
  try {
5825
- return await runMutation(db, context, info, txCtx, async (executor) => {
5826
- const supplied = single ? [args.values] : args.values;
5827
- if (!supplied.length) {
5828
- throw new GraphQLError8("No values were provided!");
5829
- }
5830
- const entries = nested?.enabled(tableName) ? supplied.map((values) => nested.split(tableName, values)) : void 0;
5831
- const nestedEntries = entries?.some((entry) => nested.hasOps(entry.ops)) ? entries : void 0;
5832
- const contextColumns = policies?.contextValues?.(tableName);
5833
- const scope = policies?.scope?.(context);
5834
- const input = nestedEntries ? [] : applyContextValuesAll(
5835
- remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : supplied, table),
5836
- contextColumns,
5837
- context
5838
- );
5839
- const parsedInfo = parseResolveInfo3(info, { deep: true });
5840
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
5841
- relationMap,
5842
- tables,
5843
- tableName,
5844
- typeName,
5845
- typeNameMapper,
5846
- table,
5847
- pkNames,
5848
- parsedInfo,
5849
- limits,
5850
- scope
5851
- });
5852
- const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
5853
- const runUpsert = async (target, values) => {
5854
- const plan = resolveConflictPlan({
5855
- table,
5856
- values,
5857
- onConflict: args.onConflict,
6193
+ return await runMutation(
6194
+ db,
6195
+ context,
6196
+ info,
6197
+ txCtx,
6198
+ async (executor) => {
6199
+ const supplied = single ? [args.values] : args.values;
6200
+ if (!supplied.length) {
6201
+ throw new GraphQLError8("No values were provided!");
6202
+ }
6203
+ await runWriteHook(hooks, "before", {
6204
+ table: tableName,
6205
+ operation: "upsert",
6206
+ single,
6207
+ args,
6208
+ context,
6209
+ info,
6210
+ tx: executor
6211
+ });
6212
+ const entries = nested?.enabled(tableName) ? supplied.map((values) => nested.split(tableName, values)) : void 0;
6213
+ const nestedEntries = entries?.some((entry) => nested.hasOps(entry.ops)) ? entries : void 0;
6214
+ const contextColumns = policies?.contextValues?.(tableName);
6215
+ const scope = policies?.scope?.(context);
6216
+ const input = nestedEntries ? [] : applyContextValuesAll(
6217
+ remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : supplied, table),
6218
+ contextColumns,
6219
+ context
6220
+ );
6221
+ const parsedInfo = parseResolveInfo3(info, { deep: true });
6222
+ const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6223
+ relationMap,
6224
+ tables,
6225
+ tableName,
6226
+ typeName,
6227
+ typeNameMapper,
6228
+ table,
5858
6229
  pkNames,
5859
- uniqueSets,
5860
- excludedRef: excludedColumnRef,
5861
- withTarget: true,
5862
- buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
6230
+ parsedInfo,
6231
+ limits,
6232
+ scope
5863
6233
  });
5864
- let query = target.insert(table).values(values).returning(returning);
5865
- const setWhere = withScope(scope, tableName, table, plan.setWhere);
5866
- query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? { target: plan.target } : void 0) : query.onConflictDoUpdate({ target: plan.target, set: plan.set, setWhere });
5867
- return await query;
5868
- };
5869
- const result = nestedEntries ? await writeWithNestedOps({
5870
- executor,
5871
- runtime: nested,
5872
- tableName,
5873
- entries: nestedEntries,
5874
- remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
5875
- write: (tx, values) => runUpsert(tx, [values]),
5876
- context
5877
- }) : await runUpsert(executor, input);
5878
- if (single && !result[0]) {
5879
- return void 0;
5880
- }
5881
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
5882
- return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
5883
- });
6234
+ const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
6235
+ const runUpsert = async (target, values) => {
6236
+ const plan = resolveConflictPlan({
6237
+ table,
6238
+ values,
6239
+ onConflict: args.onConflict,
6240
+ pkNames,
6241
+ uniqueSets,
6242
+ excludedRef: excludedColumnRef,
6243
+ withTarget: true,
6244
+ buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
6245
+ });
6246
+ let query = target.insert(table).values(values).returning(returning);
6247
+ const setWhere = withScope(scope, tableName, table, plan.setWhere);
6248
+ query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? { target: plan.target } : void 0) : query.onConflictDoUpdate({ target: plan.target, set: plan.set, setWhere });
6249
+ return await query;
6250
+ };
6251
+ const result = nestedEntries ? await writeWithNestedOps({
6252
+ executor,
6253
+ runtime: nested,
6254
+ tableName,
6255
+ entries: nestedEntries,
6256
+ remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
6257
+ write: (tx, values) => runUpsert(tx, [values]),
6258
+ context
6259
+ }) : await runUpsert(executor, input);
6260
+ await runWriteHook(hooks, "after", {
6261
+ table: tableName,
6262
+ operation: "upsert",
6263
+ single,
6264
+ args,
6265
+ rows: result,
6266
+ context,
6267
+ info,
6268
+ tx: executor
6269
+ });
6270
+ if (single && !result[0]) {
6271
+ return void 0;
6272
+ }
6273
+ const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
6274
+ return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
6275
+ },
6276
+ !!hooks
6277
+ );
5884
6278
  } catch (e) {
5885
6279
  throw toGraphQLError(e);
5886
6280
  }
@@ -5898,75 +6292,102 @@ var generateUpdate2 = (db, tableName, table, tables, relationMap, setArgs, filte
5898
6292
  }
5899
6293
  };
5900
6294
  const pkNames = pgPrimaryKeyPropNames(table);
6295
+ const hooks = policies?.onWrite?.(tableName, "update");
5901
6296
  return {
5902
6297
  name: fieldName,
5903
6298
  resolver: async (_source, args, context, info) => {
5904
6299
  try {
5905
- return await runMutation(db, context, info, txCtx, async (executor) => {
5906
- const { where, set } = args;
5907
- const scope = policies?.scope?.(context);
5908
- const parsedInfo = parseResolveInfo3(info, {
5909
- deep: true
5910
- });
5911
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
5912
- relationMap,
5913
- tables,
5914
- tableName,
5915
- typeName,
5916
- typeNameMapper,
5917
- table,
5918
- pkNames,
5919
- parsedInfo,
5920
- limits,
5921
- scope
5922
- });
5923
- const entry = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
5924
- const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
5925
- const input = stripContextValues(
5926
- remapUpdateInput(entry ? entry.columns : set, table, tableName),
5927
- policies?.contextValues?.(tableName)
5928
- );
5929
- if (!Object.keys(input).length && !nestedOps) {
5930
- throw new GraphQLError8("Unable to update with no values specified!");
5931
- }
5932
- const relationCtx = relationFilterCtx(filterCtx, tableName);
5933
- const filters = withScope(
5934
- scope,
5935
- tableName,
5936
- table,
5937
- single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0
5938
- );
5939
- if (single) {
5940
- await assertSingleMatch(executor, table, filters, fieldName);
5941
- }
5942
- const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
5943
- const runUpdate = async (target, values) => {
5944
- const writes = Object.keys(values).length > 0;
5945
- let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
5946
- if (filters) {
5947
- query = query.where(filters);
6300
+ return await runMutation(
6301
+ db,
6302
+ context,
6303
+ info,
6304
+ txCtx,
6305
+ async (executor) => {
6306
+ const { where, set } = args;
6307
+ const scope = policies?.scope?.(context);
6308
+ await runWriteHook(hooks, "before", {
6309
+ table: tableName,
6310
+ operation: "update",
6311
+ single,
6312
+ args,
6313
+ context,
6314
+ info,
6315
+ tx: executor
6316
+ });
6317
+ const parsedInfo = parseResolveInfo3(info, {
6318
+ deep: true
6319
+ });
6320
+ const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6321
+ relationMap,
6322
+ tables,
6323
+ tableName,
6324
+ typeName,
6325
+ typeNameMapper,
6326
+ table,
6327
+ pkNames,
6328
+ parsedInfo,
6329
+ limits,
6330
+ scope
6331
+ });
6332
+ const entry = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
6333
+ const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
6334
+ const input = stripContextValues(
6335
+ remapUpdateInput(entry ? entry.columns : set, table, tableName),
6336
+ policies?.contextValues?.(tableName)
6337
+ );
6338
+ if (!Object.keys(input).length && !nestedOps) {
6339
+ throw new GraphQLError8("Unable to update with no values specified!");
5948
6340
  }
5949
- return await (writes ? query.returning(returning) : query);
5950
- };
5951
- const result = nestedOps ? await updateWithNestedOps({
5952
- executor,
5953
- runtime: nested,
5954
- tableName,
5955
- columns: input,
5956
- ops: nestedOps,
5957
- remapValues: (values) => values,
5958
- write: runUpdate,
5959
- context
5960
- }) : await runUpdate(executor, input);
5961
- if (single && result.length > 1) {
5962
- throw new GraphQLError8(`${fieldName}: 'where' matched more than one row!`);
5963
- }
5964
- if (single && !result[0]) {
5965
- return void 0;
5966
- }
5967
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
5968
- return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
5969
- });
6341
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
6342
+ const filters = withScope(
6343
+ scope,
6344
+ tableName,
6345
+ table,
6346
+ single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0
6347
+ );
6348
+ if (single) {
6349
+ await assertSingleMatch(executor, table, filters, fieldName);
6350
+ }
6351
+ const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
6352
+ const runUpdate = async (target, values) => {
6353
+ const writes = Object.keys(values).length > 0;
6354
+ let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
6355
+ if (filters) {
6356
+ query = query.where(filters);
6357
+ }
6358
+ return await (writes ? query.returning(returning) : query);
6359
+ };
6360
+ const result = nestedOps ? await updateWithNestedOps({
6361
+ executor,
6362
+ runtime: nested,
6363
+ tableName,
6364
+ columns: input,
6365
+ ops: nestedOps,
6366
+ remapValues: (values) => values,
6367
+ write: runUpdate,
6368
+ context
6369
+ }) : await runUpdate(executor, input);
6370
+ await runWriteHook(hooks, "after", {
6371
+ table: tableName,
6372
+ operation: "update",
6373
+ single,
6374
+ args,
6375
+ rows: result,
6376
+ context,
6377
+ info,
6378
+ tx: executor
6379
+ });
6380
+ if (single && result.length > 1) {
6381
+ throw new GraphQLError8(`${fieldName}: 'where' matched more than one row!`);
6382
+ }
6383
+ if (single && !result[0]) {
6384
+ return void 0;
6385
+ }
6386
+ const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
6387
+ return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
6388
+ },
6389
+ !!hooks
6390
+ );
5970
6391
  } catch (e) {
5971
6392
  throw toGraphQLError(e);
5972
6393
  }
@@ -5981,94 +6402,121 @@ var generateUpdateMany2 = (db, tableName, table, tables, relationMap, updateMany
5981
6402
  }
5982
6403
  };
5983
6404
  const pkNames = pgPrimaryKeyPropNames(table);
6405
+ const hooks = policies?.onWrite?.(tableName, "updateMany");
5984
6406
  return {
5985
6407
  name: fieldName,
5986
6408
  resolver: async (_source, args, context, info) => {
5987
6409
  try {
5988
- return await runMutation(db, context, info, txCtx, async (executor) => {
5989
- const { updates } = args;
5990
- if (!updates.length) {
5991
- throw new GraphQLError8("No updates were provided!");
5992
- }
5993
- const scope = policies?.scope?.(context);
5994
- const contextColumns = policies?.contextValues?.(tableName);
5995
- const parsedInfo = parseResolveInfo3(info, {
5996
- deep: true
5997
- });
5998
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
5999
- relationMap,
6000
- tables,
6001
- tableName,
6002
- typeName,
6003
- typeNameMapper,
6004
- table,
6005
- pkNames,
6006
- parsedInfo,
6007
- limits,
6008
- scope
6009
- });
6010
- const entries = updates.map(({ where, set }) => {
6011
- const split = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
6012
- const ops = split && nested.hasOps(split.ops) ? split.ops : void 0;
6013
- const input = stripContextValues(
6014
- remapUpdateInput(split ? split.columns : set, table, tableName),
6015
- contextColumns
6016
- );
6017
- if (!Object.keys(input).length && !ops) {
6018
- throw new GraphQLError8("Unable to update with no values specified!");
6410
+ return await runMutation(
6411
+ db,
6412
+ context,
6413
+ info,
6414
+ txCtx,
6415
+ async (executor) => {
6416
+ const { updates } = args;
6417
+ if (!updates.length) {
6418
+ throw new GraphQLError8("No updates were provided!");
6019
6419
  }
6020
- return {
6021
- set: input,
6022
- ops,
6023
- filters: withScope(
6024
- scope,
6025
- tableName,
6026
- table,
6027
- where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0
6028
- )
6029
- };
6030
- });
6031
- const returning = entries.some((entry) => entry.ops) ? nested.withJoinColumns(
6032
- tableName,
6033
- mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))),
6034
- {
6035
- ...columns
6036
- },
6037
- table
6038
- ) : columns;
6039
- const perEntry = await executor.transaction(async (tx) => {
6040
- const results = [];
6041
- for (const entry of entries) {
6042
- const values = entry.ops ? { ...entry.set, ...await nested.applyParentSide(tx, tableName, entry.ops, context) } : entry.set;
6043
- const writes = Object.keys(values).length > 0;
6044
- let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
6045
- if (entry.filters) {
6046
- query = query.where(entry.filters);
6420
+ await runWriteHook(hooks, "before", {
6421
+ table: tableName,
6422
+ operation: "updateMany",
6423
+ single: false,
6424
+ args,
6425
+ context,
6426
+ info,
6427
+ tx: executor
6428
+ });
6429
+ const scope = policies?.scope?.(context);
6430
+ const contextColumns = policies?.contextValues?.(tableName);
6431
+ const parsedInfo = parseResolveInfo3(info, {
6432
+ deep: true
6433
+ });
6434
+ const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6435
+ relationMap,
6436
+ tables,
6437
+ tableName,
6438
+ typeName,
6439
+ typeNameMapper,
6440
+ table,
6441
+ pkNames,
6442
+ parsedInfo,
6443
+ limits,
6444
+ scope
6445
+ });
6446
+ const entries = updates.map(({ where, set }) => {
6447
+ const split = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
6448
+ const ops = split && nested.hasOps(split.ops) ? split.ops : void 0;
6449
+ const input = stripContextValues(
6450
+ remapUpdateInput(split ? split.columns : set, table, tableName),
6451
+ contextColumns
6452
+ );
6453
+ if (!Object.keys(input).length && !ops) {
6454
+ throw new GraphQLError8("Unable to update with no values specified!");
6047
6455
  }
6048
- const rows = await (writes ? query.returning(returning) : query);
6049
- if (entry.ops) {
6050
- await nested.applyChildSide(tx, tableName, entry.ops, rows, context);
6456
+ return {
6457
+ set: input,
6458
+ ops,
6459
+ filters: withScope(
6460
+ scope,
6461
+ tableName,
6462
+ table,
6463
+ where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0
6464
+ )
6465
+ };
6466
+ });
6467
+ const returning = entries.some((entry) => entry.ops) ? nested.withJoinColumns(
6468
+ tableName,
6469
+ mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))),
6470
+ {
6471
+ ...columns
6472
+ },
6473
+ table
6474
+ ) : columns;
6475
+ const perEntry = await executor.transaction(async (tx) => {
6476
+ const results = [];
6477
+ for (const entry of entries) {
6478
+ const values = entry.ops ? { ...entry.set, ...await nested.applyParentSide(tx, tableName, entry.ops, context) } : entry.set;
6479
+ const writes = Object.keys(values).length > 0;
6480
+ let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
6481
+ if (entry.filters) {
6482
+ query = query.where(entry.filters);
6483
+ }
6484
+ const rows = await (writes ? query.returning(returning) : query);
6485
+ if (entry.ops) {
6486
+ await nested.applyChildSide(tx, tableName, entry.ops, rows, context);
6487
+ }
6488
+ results.push(rows);
6051
6489
  }
6052
- results.push(rows);
6053
- }
6054
- return results;
6055
- });
6056
- const flatRows = perEntry.flat();
6057
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, flatRows, pkNames, withParams) : flatRows;
6058
- const output = [];
6059
- let offset = 0;
6060
- for (const rows of perEntry) {
6061
- if (!rows.length) {
6062
- output.push(null);
6063
- continue;
6064
- }
6065
- for (let i = 0; i < rows.length; i++) {
6066
- output.push(remapToGraphQLSingleOutput(enriched[offset + i], tableName, table, relationMap));
6490
+ return results;
6491
+ });
6492
+ const flatRows = perEntry.flat();
6493
+ await runWriteHook(hooks, "after", {
6494
+ table: tableName,
6495
+ operation: "updateMany",
6496
+ single: false,
6497
+ args,
6498
+ rows: flatRows,
6499
+ context,
6500
+ info,
6501
+ tx: executor
6502
+ });
6503
+ const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, flatRows, pkNames, withParams) : flatRows;
6504
+ const output = [];
6505
+ let offset = 0;
6506
+ for (const rows of perEntry) {
6507
+ if (!rows.length) {
6508
+ output.push(null);
6509
+ continue;
6510
+ }
6511
+ for (let i = 0; i < rows.length; i++) {
6512
+ output.push(remapToGraphQLSingleOutput(enriched[offset + i], tableName, table, relationMap));
6513
+ }
6514
+ offset += rows.length;
6067
6515
  }
6068
- offset += rows.length;
6069
- }
6070
- return output;
6071
- });
6516
+ return output;
6517
+ },
6518
+ !!hooks
6519
+ );
6072
6520
  } catch (e) {
6073
6521
  throw toGraphQLError(e);
6074
6522
  }
@@ -6078,6 +6526,8 @@ var generateUpdateMany2 = (db, tableName, table, tables, relationMap, updateMany
6078
6526
  };
6079
6527
  var generateDelete2 = (db, tableName, table, filterArgs, fieldName, typeName, single, requireWhere, filterCtx, selectionCtx, txCtx, policies, restore = false) => {
6080
6528
  const softDelete = policies?.softDelete?.(tableName);
6529
+ const operation = restore ? "restore" : "delete";
6530
+ const hooks = policies?.onWrite?.(tableName, operation);
6081
6531
  const queryArgs = {
6082
6532
  where: {
6083
6533
  type: single || requireWhere ? new GraphQLNonNull7(filterArgs) : filterArgs
@@ -6087,42 +6537,68 @@ var generateDelete2 = (db, tableName, table, filterArgs, fieldName, typeName, si
6087
6537
  name: fieldName,
6088
6538
  resolver: async (_source, args, context, info) => {
6089
6539
  try {
6090
- return await runMutation(db, context, info, txCtx, async (executor) => {
6091
- const { where } = args;
6092
- const scope = policies?.scope?.(context);
6093
- const parsedInfo = parseResolveInfo3(info, {
6094
- deep: true
6095
- });
6096
- const columns = extractSelectedColumnsFromTreeSQLFormat(
6097
- parsedInfo.fieldsByTypeName[typeName],
6098
- table,
6099
- selectionCtx
6100
- );
6101
- const relationCtx = relationFilterCtx(filterCtx, tableName);
6102
- const filters = withScope(
6103
- scope,
6104
- tableName,
6105
- table,
6106
- single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0,
6107
- restore ? "ONLY" : void 0
6108
- );
6109
- if (single) {
6110
- await assertSingleMatch(executor, table, filters, fieldName);
6111
- }
6112
- let query = softDelete ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
6113
- if (filters) {
6114
- query = query.where(filters);
6115
- }
6116
- query = query.returning(columns);
6117
- const result = await query;
6118
- if (single && result.length > 1) {
6119
- throw new GraphQLError8(`${fieldName}: 'where' matched more than one row!`);
6120
- }
6121
- if (single) {
6122
- return result[0] ? remapToGraphQLSingleOutput(result[0], tableName, table) : void 0;
6123
- }
6124
- return remapToGraphQLArrayOutput(result, tableName, table);
6125
- });
6540
+ return await runMutation(
6541
+ db,
6542
+ context,
6543
+ info,
6544
+ txCtx,
6545
+ async (executor) => {
6546
+ const { where } = args;
6547
+ const scope = policies?.scope?.(context);
6548
+ await runWriteHook(hooks, "before", {
6549
+ table: tableName,
6550
+ operation,
6551
+ single,
6552
+ args,
6553
+ context,
6554
+ info,
6555
+ tx: executor
6556
+ });
6557
+ const parsedInfo = parseResolveInfo3(info, {
6558
+ deep: true
6559
+ });
6560
+ const columns = extractSelectedColumnsFromTreeSQLFormat(
6561
+ parsedInfo.fieldsByTypeName[typeName],
6562
+ table,
6563
+ selectionCtx
6564
+ );
6565
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
6566
+ const filters = withScope(
6567
+ scope,
6568
+ tableName,
6569
+ table,
6570
+ single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0,
6571
+ restore ? "ONLY" : void 0
6572
+ );
6573
+ if (single) {
6574
+ await assertSingleMatch(executor, table, filters, fieldName);
6575
+ }
6576
+ let query = softDelete ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
6577
+ if (filters) {
6578
+ query = query.where(filters);
6579
+ }
6580
+ query = query.returning(columns);
6581
+ const result = await query;
6582
+ await runWriteHook(hooks, "after", {
6583
+ table: tableName,
6584
+ operation,
6585
+ single,
6586
+ args,
6587
+ rows: result,
6588
+ context,
6589
+ info,
6590
+ tx: executor
6591
+ });
6592
+ if (single && result.length > 1) {
6593
+ throw new GraphQLError8(`${fieldName}: 'where' matched more than one row!`);
6594
+ }
6595
+ if (single) {
6596
+ return result[0] ? remapToGraphQLSingleOutput(result[0], tableName, table) : void 0;
6597
+ }
6598
+ return remapToGraphQLArrayOutput(result, tableName, table);
6599
+ },
6600
+ !!hooks
6601
+ );
6126
6602
  } catch (e) {
6127
6603
  throw toGraphQLError(e);
6128
6604
  }
@@ -6853,58 +7329,85 @@ var generateInsertArray3 = (db, tableName, table, tables, relationMap, baseType,
6853
7329
  }
6854
7330
  };
6855
7331
  const pkNames = sqlitePrimaryKeyPropNames(table);
7332
+ const hooks = policies?.onWrite?.(tableName, "insert");
6856
7333
  return {
6857
7334
  name: fieldName,
6858
7335
  resolver: async (_source, args, context, info) => {
6859
7336
  try {
6860
- return await runMutation(db, context, info, txCtx, async (executor) => {
6861
- if (!args.values.length) {
6862
- throw new GraphQLError9("No values were provided!");
6863
- }
6864
- const entries = nested?.enabled(tableName) ? args.values.map((values) => nested.split(tableName, values)) : void 0;
6865
- const nestedEntries = entries?.some((entry) => nested.hasOps(entry.ops)) ? entries : void 0;
6866
- const contextColumns = policies?.contextValues?.(tableName);
6867
- const scope = policies?.scope?.(context);
6868
- const input = nestedEntries ? [] : applyContextValuesAll(
6869
- remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : args.values, table),
6870
- contextColumns,
6871
- context
6872
- );
6873
- const parsedInfo = parseResolveInfo4(info, {
6874
- deep: true
6875
- });
6876
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6877
- relationMap,
6878
- tables,
6879
- tableName,
6880
- typeName,
6881
- typeNameMapper,
6882
- table,
6883
- pkNames,
6884
- parsedInfo,
6885
- limits,
6886
- scope
6887
- });
6888
- const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
6889
- const runInsert = async (target, values) => {
6890
- let query = target.insert(table).values(values).returning(returning);
6891
- if (conflictDoNothing) {
6892
- query = query.onConflictDoNothing();
7337
+ return await runMutation(
7338
+ db,
7339
+ context,
7340
+ info,
7341
+ txCtx,
7342
+ async (executor) => {
7343
+ if (!args.values.length) {
7344
+ throw new GraphQLError9("No values were provided!");
6893
7345
  }
6894
- return await query;
6895
- };
6896
- const result = nestedEntries ? await writeWithNestedOps({
6897
- executor,
6898
- runtime: nested,
6899
- tableName,
6900
- entries: nestedEntries,
6901
- remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
6902
- write: (tx, values) => runInsert(tx, [values]),
6903
- context
6904
- }) : await runInsert(executor, input);
6905
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
6906
- return remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
6907
- });
7346
+ await runWriteHook(hooks, "before", {
7347
+ table: tableName,
7348
+ operation: "insert",
7349
+ single: false,
7350
+ args,
7351
+ context,
7352
+ info,
7353
+ tx: executor
7354
+ });
7355
+ const entries = nested?.enabled(tableName) ? args.values.map((values) => nested.split(tableName, values)) : void 0;
7356
+ const nestedEntries = entries?.some((entry) => nested.hasOps(entry.ops)) ? entries : void 0;
7357
+ const contextColumns = policies?.contextValues?.(tableName);
7358
+ const scope = policies?.scope?.(context);
7359
+ const input = nestedEntries ? [] : applyContextValuesAll(
7360
+ remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : args.values, table),
7361
+ contextColumns,
7362
+ context
7363
+ );
7364
+ const parsedInfo = parseResolveInfo4(info, {
7365
+ deep: true
7366
+ });
7367
+ const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7368
+ relationMap,
7369
+ tables,
7370
+ tableName,
7371
+ typeName,
7372
+ typeNameMapper,
7373
+ table,
7374
+ pkNames,
7375
+ parsedInfo,
7376
+ limits,
7377
+ scope
7378
+ });
7379
+ const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
7380
+ const runInsert = async (target, values) => {
7381
+ let query = target.insert(table).values(values).returning(returning);
7382
+ if (conflictDoNothing) {
7383
+ query = query.onConflictDoNothing();
7384
+ }
7385
+ return await query;
7386
+ };
7387
+ const result = nestedEntries ? await writeWithNestedOps({
7388
+ executor,
7389
+ runtime: nested,
7390
+ tableName,
7391
+ entries: nestedEntries,
7392
+ remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
7393
+ write: (tx, values) => runInsert(tx, [values]),
7394
+ context
7395
+ }) : await runInsert(executor, input);
7396
+ await runWriteHook(hooks, "after", {
7397
+ table: tableName,
7398
+ operation: "insert",
7399
+ single: false,
7400
+ args,
7401
+ rows: result,
7402
+ context,
7403
+ info,
7404
+ tx: executor
7405
+ });
7406
+ const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
7407
+ return remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
7408
+ },
7409
+ !!hooks
7410
+ );
6908
7411
  } catch (e) {
6909
7412
  throw toGraphQLError(e);
6910
7413
  }
@@ -6919,61 +7422,88 @@ var generateInsertSingle3 = (db, tableName, table, tables, relationMap, baseType
6919
7422
  }
6920
7423
  };
6921
7424
  const pkNames = sqlitePrimaryKeyPropNames(table);
7425
+ const hooks = policies?.onWrite?.(tableName, "insert");
6922
7426
  return {
6923
7427
  name: fieldName,
6924
7428
  resolver: async (_source, args, context, info) => {
6925
7429
  try {
6926
- return await runMutation(db, context, info, txCtx, async (executor) => {
6927
- const entry = nested?.enabled(tableName) ? nested.split(tableName, args.values) : void 0;
6928
- const nestedEntry = entry && nested.hasOps(entry.ops) ? entry : void 0;
6929
- const contextColumns = policies?.contextValues?.(tableName);
6930
- const scope = policies?.scope?.(context);
6931
- const input = nestedEntry ? {} : applyContextValues(
6932
- remapFromGraphQLSingleInput(entry ? entry.columns : args.values, table),
6933
- contextColumns,
6934
- context
6935
- );
6936
- const parsedInfo = parseResolveInfo4(info, {
6937
- deep: true
6938
- });
6939
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6940
- relationMap,
6941
- tables,
6942
- tableName,
6943
- typeName,
6944
- typeNameMapper,
6945
- table,
6946
- pkNames,
6947
- parsedInfo,
6948
- limits,
6949
- scope
6950
- });
6951
- const returning = nestedEntry ? nested.withJoinColumns(tableName, nestedEntry.ops, { ...columns }, table) : columns;
6952
- const runInsert = async (target, values) => {
6953
- let query = target.insert(table).values(values).returning(returning);
6954
- if (conflictDoNothing) {
6955
- query = query.onConflictDoNothing();
6956
- }
6957
- return await query;
6958
- };
6959
- const result = nestedEntry ? await writeWithNestedOps({
6960
- executor,
6961
- runtime: nested,
6962
- tableName,
6963
- entries: [nestedEntry],
6964
- remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
6965
- write: runInsert,
6966
- context
6967
- }) : await runInsert(executor, input);
6968
- if (!result[0]) {
6969
- if (!conflictDoNothing) {
6970
- throw new GraphQLError9(`${fieldName}: the insert returned no row.`);
7430
+ return await runMutation(
7431
+ db,
7432
+ context,
7433
+ info,
7434
+ txCtx,
7435
+ async (executor) => {
7436
+ await runWriteHook(hooks, "before", {
7437
+ table: tableName,
7438
+ operation: "insert",
7439
+ single: true,
7440
+ args,
7441
+ context,
7442
+ info,
7443
+ tx: executor
7444
+ });
7445
+ const entry = nested?.enabled(tableName) ? nested.split(tableName, args.values) : void 0;
7446
+ const nestedEntry = entry && nested.hasOps(entry.ops) ? entry : void 0;
7447
+ const contextColumns = policies?.contextValues?.(tableName);
7448
+ const scope = policies?.scope?.(context);
7449
+ const input = nestedEntry ? {} : applyContextValues(
7450
+ remapFromGraphQLSingleInput(entry ? entry.columns : args.values, table),
7451
+ contextColumns,
7452
+ context
7453
+ );
7454
+ const parsedInfo = parseResolveInfo4(info, {
7455
+ deep: true
7456
+ });
7457
+ const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7458
+ relationMap,
7459
+ tables,
7460
+ tableName,
7461
+ typeName,
7462
+ typeNameMapper,
7463
+ table,
7464
+ pkNames,
7465
+ parsedInfo,
7466
+ limits,
7467
+ scope
7468
+ });
7469
+ const returning = nestedEntry ? nested.withJoinColumns(tableName, nestedEntry.ops, { ...columns }, table) : columns;
7470
+ const runInsert = async (target, values) => {
7471
+ let query = target.insert(table).values(values).returning(returning);
7472
+ if (conflictDoNothing) {
7473
+ query = query.onConflictDoNothing();
7474
+ }
7475
+ return await query;
7476
+ };
7477
+ const result = nestedEntry ? await writeWithNestedOps({
7478
+ executor,
7479
+ runtime: nested,
7480
+ tableName,
7481
+ entries: [nestedEntry],
7482
+ remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
7483
+ write: runInsert,
7484
+ context
7485
+ }) : await runInsert(executor, input);
7486
+ await runWriteHook(hooks, "after", {
7487
+ table: tableName,
7488
+ operation: "insert",
7489
+ single: true,
7490
+ args,
7491
+ rows: result,
7492
+ context,
7493
+ info,
7494
+ tx: executor
7495
+ });
7496
+ if (!result[0]) {
7497
+ if (!conflictDoNothing) {
7498
+ throw new GraphQLError9(`${fieldName}: the insert returned no row.`);
7499
+ }
7500
+ return void 0;
6971
7501
  }
6972
- return void 0;
6973
- }
6974
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
6975
- return remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap);
6976
- });
7502
+ const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
7503
+ return remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap);
7504
+ },
7505
+ !!hooks
7506
+ );
6977
7507
  } catch (e) {
6978
7508
  throw toGraphQLError(e);
6979
7509
  }
@@ -6992,69 +7522,96 @@ var generateUpsert3 = (db, tableName, table, tables, relationMap, baseType, onCo
6992
7522
  }
6993
7523
  };
6994
7524
  const pkNames = sqlitePrimaryKeyPropNames(table);
7525
+ const hooks = policies?.onWrite?.(tableName, "upsert");
6995
7526
  return {
6996
7527
  name: fieldName,
6997
7528
  resolver: async (_source, args, context, info) => {
6998
7529
  try {
6999
- return await runMutation(db, context, info, txCtx, async (executor) => {
7000
- const supplied = single ? [args.values] : args.values;
7001
- if (!supplied.length) {
7002
- throw new GraphQLError9("No values were provided!");
7003
- }
7004
- const entries = nested?.enabled(tableName) ? supplied.map((values) => nested.split(tableName, values)) : void 0;
7005
- const nestedEntries = entries?.some((entry) => nested.hasOps(entry.ops)) ? entries : void 0;
7006
- const contextColumns = policies?.contextValues?.(tableName);
7007
- const scope = policies?.scope?.(context);
7008
- const input = nestedEntries ? [] : applyContextValuesAll(
7009
- remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : supplied, table),
7010
- contextColumns,
7011
- context
7012
- );
7013
- const parsedInfo = parseResolveInfo4(info, { deep: true });
7014
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7015
- relationMap,
7016
- tables,
7017
- tableName,
7018
- typeName,
7019
- typeNameMapper,
7020
- table,
7021
- pkNames,
7022
- parsedInfo,
7023
- limits,
7024
- scope
7025
- });
7026
- const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
7027
- const runUpsert = async (target, values) => {
7028
- const plan = resolveConflictPlan({
7530
+ return await runMutation(
7531
+ db,
7532
+ context,
7533
+ info,
7534
+ txCtx,
7535
+ async (executor) => {
7536
+ const supplied = single ? [args.values] : args.values;
7537
+ if (!supplied.length) {
7538
+ throw new GraphQLError9("No values were provided!");
7539
+ }
7540
+ await runWriteHook(hooks, "before", {
7541
+ table: tableName,
7542
+ operation: "upsert",
7543
+ single,
7544
+ args,
7545
+ context,
7546
+ info,
7547
+ tx: executor
7548
+ });
7549
+ const entries = nested?.enabled(tableName) ? supplied.map((values) => nested.split(tableName, values)) : void 0;
7550
+ const nestedEntries = entries?.some((entry) => nested.hasOps(entry.ops)) ? entries : void 0;
7551
+ const contextColumns = policies?.contextValues?.(tableName);
7552
+ const scope = policies?.scope?.(context);
7553
+ const input = nestedEntries ? [] : applyContextValuesAll(
7554
+ remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : supplied, table),
7555
+ contextColumns,
7556
+ context
7557
+ );
7558
+ const parsedInfo = parseResolveInfo4(info, { deep: true });
7559
+ const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7560
+ relationMap,
7561
+ tables,
7562
+ tableName,
7563
+ typeName,
7564
+ typeNameMapper,
7029
7565
  table,
7030
- values,
7031
- onConflict: args.onConflict,
7032
7566
  pkNames,
7033
- uniqueSets,
7034
- excludedRef: excludedColumnRef,
7035
- withTarget: true,
7036
- buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
7567
+ parsedInfo,
7568
+ limits,
7569
+ scope
7037
7570
  });
7038
- let query = target.insert(table).values(values).returning(returning);
7039
- const setWhere = withScope(scope, tableName, table, plan.setWhere);
7040
- query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? { target: plan.target } : void 0) : query.onConflictDoUpdate({ target: plan.target, set: plan.set, setWhere });
7041
- return await query;
7042
- };
7043
- const result = nestedEntries ? await writeWithNestedOps({
7044
- executor,
7045
- runtime: nested,
7046
- tableName,
7047
- entries: nestedEntries,
7048
- remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
7049
- write: (tx, values) => runUpsert(tx, [values]),
7050
- context
7051
- }) : await runUpsert(executor, input);
7052
- if (single && !result[0]) {
7053
- return void 0;
7054
- }
7055
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
7056
- return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
7057
- });
7571
+ const returning = nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns;
7572
+ const runUpsert = async (target, values) => {
7573
+ const plan = resolveConflictPlan({
7574
+ table,
7575
+ values,
7576
+ onConflict: args.onConflict,
7577
+ pkNames,
7578
+ uniqueSets,
7579
+ excludedRef: excludedColumnRef,
7580
+ withTarget: true,
7581
+ buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
7582
+ });
7583
+ let query = target.insert(table).values(values).returning(returning);
7584
+ const setWhere = withScope(scope, tableName, table, plan.setWhere);
7585
+ query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? { target: plan.target } : void 0) : query.onConflictDoUpdate({ target: plan.target, set: plan.set, setWhere });
7586
+ return await query;
7587
+ };
7588
+ const result = nestedEntries ? await writeWithNestedOps({
7589
+ executor,
7590
+ runtime: nested,
7591
+ tableName,
7592
+ entries: nestedEntries,
7593
+ remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
7594
+ write: (tx, values) => runUpsert(tx, [values]),
7595
+ context
7596
+ }) : await runUpsert(executor, input);
7597
+ await runWriteHook(hooks, "after", {
7598
+ table: tableName,
7599
+ operation: "upsert",
7600
+ single,
7601
+ args,
7602
+ rows: result,
7603
+ context,
7604
+ info,
7605
+ tx: executor
7606
+ });
7607
+ if (single && !result[0]) {
7608
+ return void 0;
7609
+ }
7610
+ const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
7611
+ return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
7612
+ },
7613
+ !!hooks
7614
+ );
7058
7615
  } catch (e) {
7059
7616
  throw toGraphQLError(e);
7060
7617
  }
@@ -7072,75 +7629,102 @@ var generateUpdate3 = (db, tableName, table, tables, relationMap, setArgs, filte
7072
7629
  }
7073
7630
  };
7074
7631
  const pkNames = sqlitePrimaryKeyPropNames(table);
7632
+ const hooks = policies?.onWrite?.(tableName, "update");
7075
7633
  return {
7076
7634
  name: fieldName,
7077
7635
  resolver: async (_source, args, context, info) => {
7078
7636
  try {
7079
- return await runMutation(db, context, info, txCtx, async (executor) => {
7080
- const { where, set } = args;
7081
- const scope = policies?.scope?.(context);
7082
- const parsedInfo = parseResolveInfo4(info, {
7083
- deep: true
7084
- });
7085
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7086
- relationMap,
7087
- tables,
7088
- tableName,
7089
- typeName,
7090
- typeNameMapper,
7091
- table,
7092
- pkNames,
7093
- parsedInfo,
7094
- limits,
7095
- scope
7096
- });
7097
- const entry = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
7098
- const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
7099
- const input = stripContextValues(
7100
- remapUpdateInput(entry ? entry.columns : set, table, tableName),
7101
- policies?.contextValues?.(tableName)
7102
- );
7103
- if (!Object.keys(input).length && !nestedOps) {
7104
- throw new GraphQLError9("Unable to update with no values specified!");
7105
- }
7106
- const relationCtx = relationFilterCtx(filterCtx, tableName);
7107
- const filters = withScope(
7108
- scope,
7109
- tableName,
7110
- table,
7111
- single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0
7112
- );
7113
- if (single) {
7114
- await assertSingleMatch(executor, table, filters, fieldName);
7115
- }
7116
- const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
7117
- const runUpdate = async (target, values) => {
7118
- const writes = Object.keys(values).length > 0;
7119
- let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
7120
- if (filters) {
7121
- query = query.where(filters);
7637
+ return await runMutation(
7638
+ db,
7639
+ context,
7640
+ info,
7641
+ txCtx,
7642
+ async (executor) => {
7643
+ await runWriteHook(hooks, "before", {
7644
+ table: tableName,
7645
+ operation: "update",
7646
+ single,
7647
+ args,
7648
+ context,
7649
+ info,
7650
+ tx: executor
7651
+ });
7652
+ const { where, set } = args;
7653
+ const scope = policies?.scope?.(context);
7654
+ const parsedInfo = parseResolveInfo4(info, {
7655
+ deep: true
7656
+ });
7657
+ const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7658
+ relationMap,
7659
+ tables,
7660
+ tableName,
7661
+ typeName,
7662
+ typeNameMapper,
7663
+ table,
7664
+ pkNames,
7665
+ parsedInfo,
7666
+ limits,
7667
+ scope
7668
+ });
7669
+ const entry = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
7670
+ const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
7671
+ const input = stripContextValues(
7672
+ remapUpdateInput(entry ? entry.columns : set, table, tableName),
7673
+ policies?.contextValues?.(tableName)
7674
+ );
7675
+ if (!Object.keys(input).length && !nestedOps) {
7676
+ throw new GraphQLError9("Unable to update with no values specified!");
7122
7677
  }
7123
- return await (writes ? query.returning(returning) : query);
7124
- };
7125
- const result = nestedOps ? await updateWithNestedOps({
7126
- executor,
7127
- runtime: nested,
7128
- tableName,
7129
- columns: input,
7130
- ops: nestedOps,
7131
- remapValues: (values) => values,
7132
- write: runUpdate,
7133
- context
7134
- }) : await runUpdate(executor, input);
7135
- if (single && result.length > 1) {
7136
- throw new GraphQLError9(`${fieldName}: 'where' matched more than one row!`);
7137
- }
7138
- if (single && !result[0]) {
7139
- return void 0;
7140
- }
7141
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
7142
- return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
7143
- });
7678
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
7679
+ const filters = withScope(
7680
+ scope,
7681
+ tableName,
7682
+ table,
7683
+ single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0
7684
+ );
7685
+ if (single) {
7686
+ await assertSingleMatch(executor, table, filters, fieldName);
7687
+ }
7688
+ const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
7689
+ const runUpdate = async (target, values) => {
7690
+ const writes = Object.keys(values).length > 0;
7691
+ let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
7692
+ if (filters) {
7693
+ query = query.where(filters);
7694
+ }
7695
+ return await (writes ? query.returning(returning) : query);
7696
+ };
7697
+ const result = nestedOps ? await updateWithNestedOps({
7698
+ executor,
7699
+ runtime: nested,
7700
+ tableName,
7701
+ columns: input,
7702
+ ops: nestedOps,
7703
+ remapValues: (values) => values,
7704
+ write: runUpdate,
7705
+ context
7706
+ }) : await runUpdate(executor, input);
7707
+ await runWriteHook(hooks, "after", {
7708
+ table: tableName,
7709
+ operation: "update",
7710
+ single,
7711
+ args,
7712
+ rows: result,
7713
+ context,
7714
+ info,
7715
+ tx: executor
7716
+ });
7717
+ if (single && result.length > 1) {
7718
+ throw new GraphQLError9(`${fieldName}: 'where' matched more than one row!`);
7719
+ }
7720
+ if (single && !result[0]) {
7721
+ return void 0;
7722
+ }
7723
+ const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
7724
+ return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
7725
+ },
7726
+ !!hooks
7727
+ );
7144
7728
  } catch (e) {
7145
7729
  throw toGraphQLError(e);
7146
7730
  }
@@ -7155,122 +7739,149 @@ var generateUpdateMany3 = (db, tableName, table, tables, relationMap, updateMany
7155
7739
  }
7156
7740
  };
7157
7741
  const pkNames = sqlitePrimaryKeyPropNames(table);
7742
+ const hooks = policies?.onWrite?.(tableName, "updateMany");
7158
7743
  return {
7159
7744
  name: fieldName,
7160
7745
  resolver: async (_source, args, context, info) => {
7161
7746
  try {
7162
- return await runMutation(db, context, info, txCtx, async (executor) => {
7163
- const { updates } = args;
7164
- if (!updates.length) {
7165
- throw new GraphQLError9("No updates were provided!");
7166
- }
7167
- const scope = policies?.scope?.(context);
7168
- const contextColumns = policies?.contextValues?.(tableName);
7169
- const parsedInfo = parseResolveInfo4(info, {
7170
- deep: true
7171
- });
7172
- const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7173
- relationMap,
7174
- tables,
7175
- tableName,
7176
- typeName,
7177
- typeNameMapper,
7178
- table,
7179
- pkNames,
7180
- parsedInfo,
7181
- limits,
7182
- scope
7183
- });
7184
- const entries = updates.map(({ where, set }) => {
7185
- const split = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
7186
- const ops = split && nested.hasOps(split.ops) ? split.ops : void 0;
7187
- const input = stripContextValues(
7188
- remapUpdateInput(split ? split.columns : set, table, tableName),
7189
- contextColumns
7190
- );
7191
- if (!Object.keys(input).length && !ops) {
7192
- throw new GraphQLError9("Unable to update with no values specified!");
7747
+ return await runMutation(
7748
+ db,
7749
+ context,
7750
+ info,
7751
+ txCtx,
7752
+ async (executor) => {
7753
+ const { updates } = args;
7754
+ if (!updates.length) {
7755
+ throw new GraphQLError9("No updates were provided!");
7193
7756
  }
7194
- return {
7195
- set: input,
7196
- ops,
7197
- filters: withScope(
7198
- scope,
7199
- tableName,
7200
- table,
7201
- where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0
7202
- )
7757
+ await runWriteHook(hooks, "before", {
7758
+ table: tableName,
7759
+ operation: "updateMany",
7760
+ single: false,
7761
+ args,
7762
+ context,
7763
+ info,
7764
+ tx: executor
7765
+ });
7766
+ const scope = policies?.scope?.(context);
7767
+ const contextColumns = policies?.contextValues?.(tableName);
7768
+ const parsedInfo = parseResolveInfo4(info, {
7769
+ deep: true
7770
+ });
7771
+ const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7772
+ relationMap,
7773
+ tables,
7774
+ tableName,
7775
+ typeName,
7776
+ typeNameMapper,
7777
+ table,
7778
+ pkNames,
7779
+ parsedInfo,
7780
+ limits,
7781
+ scope
7782
+ });
7783
+ const entries = updates.map(({ where, set }) => {
7784
+ const split = nested?.enabled(tableName) ? nested.split(tableName, set) : void 0;
7785
+ const ops = split && nested.hasOps(split.ops) ? split.ops : void 0;
7786
+ const input = stripContextValues(
7787
+ remapUpdateInput(split ? split.columns : set, table, tableName),
7788
+ contextColumns
7789
+ );
7790
+ if (!Object.keys(input).length && !ops) {
7791
+ throw new GraphQLError9("Unable to update with no values specified!");
7792
+ }
7793
+ return {
7794
+ set: input,
7795
+ ops,
7796
+ filters: withScope(
7797
+ scope,
7798
+ tableName,
7799
+ table,
7800
+ where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0
7801
+ )
7802
+ };
7803
+ });
7804
+ const anyNested = entries.some((entry) => entry.ops);
7805
+ const returning = anyNested ? nested.withJoinColumns(
7806
+ tableName,
7807
+ mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))),
7808
+ { ...columns },
7809
+ table
7810
+ ) : columns;
7811
+ const runEntry = (tx, entry) => {
7812
+ let query = tx.update(table).set(entry.set);
7813
+ if (entry.filters) {
7814
+ query = query.where(entry.filters);
7815
+ }
7816
+ return query.returning(returning).all();
7203
7817
  };
7204
- });
7205
- const anyNested = entries.some((entry) => entry.ops);
7206
- const returning = anyNested ? nested.withJoinColumns(
7207
- tableName,
7208
- mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))),
7209
- { ...columns },
7210
- table
7211
- ) : columns;
7212
- const runEntry = (tx, entry) => {
7213
- let query = tx.update(table).set(entry.set);
7214
- if (entry.filters) {
7215
- query = query.where(entry.filters);
7216
- }
7217
- return query.returning(returning).all();
7218
- };
7219
- const runNestedEntry = async (tx, entry) => {
7220
- const values = entry.ops ? { ...entry.set, ...await nested.applyParentSide(tx, tableName, entry.ops, context) } : entry.set;
7221
- const writes = Object.keys(values).length > 0;
7222
- let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
7223
- if (entry.filters) {
7224
- query = query.where(entry.filters);
7225
- }
7226
- const rows = await (writes ? query.returning(returning) : query);
7227
- if (entry.ops) {
7228
- await nested.applyChildSide(tx, tableName, entry.ops, rows, context);
7229
- }
7230
- return rows;
7231
- };
7232
- const perEntry = await executor.transaction((tx) => {
7233
- if (anyNested) {
7234
- return (async () => {
7235
- const results2 = [];
7236
- for (const entry of entries) {
7237
- results2.push(await runNestedEntry(tx, entry));
7238
- }
7239
- return results2;
7240
- })();
7241
- }
7242
- const first = runEntry(tx, entries[0]);
7243
- if (typeof first?.then === "function") {
7244
- return (async () => {
7245
- const results2 = [await first];
7246
- for (let i = 1; i < entries.length; i++) {
7247
- results2.push(await runEntry(tx, entries[i]));
7248
- }
7249
- return results2;
7250
- })();
7251
- }
7252
- const results = [first];
7253
- for (let i = 1; i < entries.length; i++) {
7254
- results.push(runEntry(tx, entries[i]));
7255
- }
7256
- return results;
7257
- });
7258
- const flatRows = perEntry.flat();
7259
- const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, flatRows, pkNames, withParams) : flatRows;
7260
- const output = [];
7261
- let offset = 0;
7262
- for (const rows of perEntry) {
7263
- if (!rows.length) {
7264
- output.push(null);
7265
- continue;
7266
- }
7267
- for (let i = 0; i < rows.length; i++) {
7268
- output.push(remapToGraphQLSingleOutput(enriched[offset + i], tableName, table, relationMap));
7818
+ const runNestedEntry = async (tx, entry) => {
7819
+ const values = entry.ops ? { ...entry.set, ...await nested.applyParentSide(tx, tableName, entry.ops, context) } : entry.set;
7820
+ const writes = Object.keys(values).length > 0;
7821
+ let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
7822
+ if (entry.filters) {
7823
+ query = query.where(entry.filters);
7824
+ }
7825
+ const rows = await (writes ? query.returning(returning) : query);
7826
+ if (entry.ops) {
7827
+ await nested.applyChildSide(tx, tableName, entry.ops, rows, context);
7828
+ }
7829
+ return rows;
7830
+ };
7831
+ const perEntry = await executor.transaction((tx) => {
7832
+ if (anyNested) {
7833
+ return (async () => {
7834
+ const results2 = [];
7835
+ for (const entry of entries) {
7836
+ results2.push(await runNestedEntry(tx, entry));
7837
+ }
7838
+ return results2;
7839
+ })();
7840
+ }
7841
+ const first = runEntry(tx, entries[0]);
7842
+ if (typeof first?.then === "function") {
7843
+ return (async () => {
7844
+ const results2 = [await first];
7845
+ for (let i = 1; i < entries.length; i++) {
7846
+ results2.push(await runEntry(tx, entries[i]));
7847
+ }
7848
+ return results2;
7849
+ })();
7850
+ }
7851
+ const results = [first];
7852
+ for (let i = 1; i < entries.length; i++) {
7853
+ results.push(runEntry(tx, entries[i]));
7854
+ }
7855
+ return results;
7856
+ });
7857
+ const flatRows = perEntry.flat();
7858
+ await runWriteHook(hooks, "after", {
7859
+ table: tableName,
7860
+ operation: "updateMany",
7861
+ single: false,
7862
+ args,
7863
+ rows: flatRows,
7864
+ context,
7865
+ info,
7866
+ tx: executor
7867
+ });
7868
+ const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, flatRows, pkNames, withParams) : flatRows;
7869
+ const output = [];
7870
+ let offset = 0;
7871
+ for (const rows of perEntry) {
7872
+ if (!rows.length) {
7873
+ output.push(null);
7874
+ continue;
7875
+ }
7876
+ for (let i = 0; i < rows.length; i++) {
7877
+ output.push(remapToGraphQLSingleOutput(enriched[offset + i], tableName, table, relationMap));
7878
+ }
7879
+ offset += rows.length;
7269
7880
  }
7270
- offset += rows.length;
7271
- }
7272
- return output;
7273
- });
7881
+ return output;
7882
+ },
7883
+ !!hooks
7884
+ );
7274
7885
  } catch (e) {
7275
7886
  throw toGraphQLError(e);
7276
7887
  }
@@ -7280,51 +7891,79 @@ var generateUpdateMany3 = (db, tableName, table, tables, relationMap, updateMany
7280
7891
  };
7281
7892
  var generateDelete3 = (db, tableName, table, filterArgs, fieldName, typeName, single, requireWhere, filterCtx, selectionCtx, txCtx, policies, restore = false) => {
7282
7893
  const softDelete = policies?.softDelete?.(tableName);
7894
+ const operation = restore ? "restore" : "delete";
7283
7895
  const queryArgs = {
7284
7896
  where: {
7285
7897
  type: single || requireWhere ? new GraphQLNonNull8(filterArgs) : filterArgs
7286
7898
  }
7287
7899
  };
7900
+ const hooks = policies?.onWrite?.(tableName, operation);
7288
7901
  return {
7289
7902
  name: fieldName,
7290
7903
  resolver: async (_source, args, context, info) => {
7291
7904
  try {
7292
- return await runMutation(db, context, info, txCtx, async (executor) => {
7293
- const { where } = args;
7294
- const scope = policies?.scope?.(context);
7295
- const parsedInfo = parseResolveInfo4(info, {
7296
- deep: true
7297
- });
7298
- const columns = extractSelectedColumnsFromTreeSQLFormat(
7299
- parsedInfo.fieldsByTypeName[typeName],
7300
- table,
7301
- selectionCtx
7302
- );
7303
- const relationCtx = relationFilterCtx(filterCtx, tableName);
7304
- const filters = withScope(
7305
- scope,
7306
- tableName,
7307
- table,
7308
- single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0,
7309
- restore ? "ONLY" : void 0
7310
- );
7311
- if (single) {
7312
- await assertSingleMatch(executor, table, filters, fieldName);
7313
- }
7314
- let query = softDelete ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
7315
- if (filters) {
7316
- query = query.where(filters);
7317
- }
7318
- query = query.returning(columns);
7319
- const result = await query;
7320
- if (single && result.length > 1) {
7321
- throw new GraphQLError9(`${fieldName}: 'where' matched more than one row!`);
7322
- }
7323
- if (single) {
7324
- return result[0] ? remapToGraphQLSingleOutput(result[0], tableName, table) : void 0;
7325
- }
7326
- return remapToGraphQLArrayOutput(result, tableName, table);
7327
- });
7905
+ return await runMutation(
7906
+ db,
7907
+ context,
7908
+ info,
7909
+ txCtx,
7910
+ async (executor) => {
7911
+ await runWriteHook(hooks, "before", {
7912
+ table: tableName,
7913
+ operation,
7914
+ single,
7915
+ args,
7916
+ context,
7917
+ info,
7918
+ tx: executor
7919
+ });
7920
+ const { where } = args;
7921
+ const scope = policies?.scope?.(context);
7922
+ const parsedInfo = parseResolveInfo4(info, {
7923
+ deep: true
7924
+ });
7925
+ const columns = extractSelectedColumnsFromTreeSQLFormat(
7926
+ parsedInfo.fieldsByTypeName[typeName],
7927
+ table,
7928
+ selectionCtx
7929
+ );
7930
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
7931
+ const filters = withScope(
7932
+ scope,
7933
+ tableName,
7934
+ table,
7935
+ single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0,
7936
+ restore ? "ONLY" : void 0
7937
+ );
7938
+ if (single) {
7939
+ await assertSingleMatch(executor, table, filters, fieldName);
7940
+ }
7941
+ let query = softDelete ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
7942
+ if (filters) {
7943
+ query = query.where(filters);
7944
+ }
7945
+ query = query.returning(columns);
7946
+ const result = await query;
7947
+ await runWriteHook(hooks, "after", {
7948
+ table: tableName,
7949
+ operation,
7950
+ single,
7951
+ args,
7952
+ rows: result,
7953
+ context,
7954
+ info,
7955
+ tx: executor
7956
+ });
7957
+ if (single && result.length > 1) {
7958
+ throw new GraphQLError9(`${fieldName}: 'where' matched more than one row!`);
7959
+ }
7960
+ if (single) {
7961
+ return result[0] ? remapToGraphQLSingleOutput(result[0], tableName, table) : void 0;
7962
+ }
7963
+ return remapToGraphQLArrayOutput(result, tableName, table);
7964
+ },
7965
+ !!hooks
7966
+ );
7328
7967
  } catch (e) {
7329
7968
  throw toGraphQLError(e);
7330
7969
  }
@@ -8209,10 +8848,36 @@ var buildSchema = (db, config) => {
8209
8848
  }
8210
8849
  }
8211
8850
  }
8212
- const policies = scopeConfig || contextValuesConfig || softDeleteInfos.size ? {
8851
+ const onWriteConfig = config?.onWrite;
8852
+ const writeHooks = /* @__PURE__ */ new Map();
8853
+ let globalWriteHooks;
8854
+ if (onWriteConfig) {
8855
+ const isGlobal = typeof onWriteConfig === "function" || typeof onWriteConfig.before === "function" || typeof onWriteConfig.after === "function";
8856
+ if (isGlobal) {
8857
+ globalWriteHooks = normalizeWriteHooks(onWriteConfig);
8858
+ } else {
8859
+ const tableNames = new Set(tableKeys);
8860
+ for (const [tableName, hooks] of Object.entries(onWriteConfig)) {
8861
+ if (!tableNames.has(tableName)) {
8862
+ throw new Error(
8863
+ `Drizzle-GraphQL Error: config.onWrite names '${tableName}', which is not a table in the Drizzle schema.`
8864
+ );
8865
+ }
8866
+ if (typeof hooks !== "function" && typeof hooks !== "object") {
8867
+ throw new Error(
8868
+ `Drizzle-GraphQL Error: config.onWrite.${tableName} must be a function or an object with 'before' and/or 'after'.`
8869
+ );
8870
+ }
8871
+ writeHooks.set(tableName, normalizeWriteHooks(hooks));
8872
+ }
8873
+ }
8874
+ }
8875
+ const onWrite = globalWriteHooks || writeHooks.size ? (tableName) => globalWriteHooks ?? writeHooks.get(tableName) : void 0;
8876
+ const policies = scopeConfig || contextValuesConfig || softDeleteInfos.size || onWrite ? {
8213
8877
  scope: scopeConfig ? (tableName) => scopeConfig[tableName] : void 0,
8214
8878
  contextValues: contextValuesConfig ? (tableName) => contextValuesConfig[tableName] : void 0,
8215
- softDelete: softDeleteInfos.size ? (tableName) => softDeleteInfos.get(tableName) : void 0
8879
+ softDelete: softDeleteInfos.size ? (tableName) => softDeleteInfos.get(tableName) : void 0,
8880
+ onWrite
8216
8881
  } : void 0;
8217
8882
  const exclude = config?.exclude;
8218
8883
  if (exclude) {