@dbsp/core 1.1.0 → 1.2.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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _dbsp_types from '@dbsp/types';
2
- import { PseudoColumnMetadata, RecursiveMetadata, RelationIR, RelationKind, AdapterCapabilities, Adapter, BaseAdapter, DDLGeneratingAdapter, ExecutingAdapter, IntrospectingAdapter, RawSqlAdapter, StreamingAdapter, TransactionalAdapter, ColumnType, WhereIntent, OnDeleteAction, ModelIR, DbCasing, QueryIntent, PlanOptions, PlanReport, RecursiveIntent, RecursivePlanOptions, RecursivePlanReport, TableIR, EnumIR, SequenceIR, AmbiguityCheckResult, DialectCapabilities, DDLFeature, DDLFeatureVersionRange, ComparisonOperator, WhereSubqueryIntent, SubqueryRefIntent, WhereAndIntent, WhereAnyIntent, WhereComparisonIntent, WhereRelationFilterIntent, RecursiveExistsOptions, WhereExistsIntent, WhereInIntent, WhereNullIntent, WhereLikeIntent, WhereNotIntent, WhereNotExistsIntent, WhereOrIntent, WhereRawExistsIntent, WhereRawNotExistsIntent, SelectIntent, IncludeIntent, AggregateIntent, OrderByIntent, SortDirection, InsertIntent, UpdateIntent, BatchUpdateIntent, DeleteIntent, UpsertIntent, Dump, SetOperationIntent, LockStrength, LockWaitPolicy, DumpMeta, JoinIntent, UnnestCteIntent, CteQueryIntent, CompileOptions, CompiledQuery, UpsertConflictTarget, UpsertConflictAction, RawCteIntent, TruncateOptions, VacuumOptions, AlterColumnOptions, CreateIndexOptions, DropIndexOptions, IndexInfo, ExpressionIntent, WhereExpressionIntent, AggOrderByArg, RelationType as RelationType$1, Cardinality, FeatureWarning, UnsupportedFeatureBehavior, FeatureBehaviorConfig, RangeValue, WhereRangeIntent, SubqueryIncludeInfo } from '@dbsp/types';
2
+ import { PseudoColumnMetadata, RecursiveMetadata, RelationIR, RelationKind, AdapterCapabilities, Adapter, BaseAdapter, DDLGeneratingAdapter, ExecutingAdapter, IntrospectingAdapter, RawSqlAdapter, StreamingAdapter, TransactionalAdapter, ColumnType, WhereIntent, OnDeleteAction, ModelIR, DbCasing, QueryIntent, PlanOptions, PlanReport, RecursiveIntent, RecursivePlanOptions, RecursivePlanReport, TableIR, EnumIR, SequenceIR, AmbiguityCheckResult, DialectCapabilities, DDLFeature, DDLFeatureVersionRange, ComparisonOperator, WhereSubqueryIntent, SubqueryRefIntent, WhereAndIntent, WhereAnyIntent, WhereComparisonIntent, WhereRelationFilterIntent, RecursiveExistsOptions, WhereExistsIntent, WhereInIntent, WhereNullIntent, WhereLikeIntent, WhereNotIntent, WhereNotExistsIntent, WhereOrIntent, WhereRawExistsIntent, WhereRawNotExistsIntent, SelectIntent, IncludeIntent, AggregateIntent, OrderByIntent, SortDirection, InsertIntent, InsertFromIntent, UpdateIntent, BatchUpdateIntent, DeleteIntent, UpsertIntent, UpsertFromIntent, Dump, SetOperationIntent, LockStrength, LockWaitPolicy, DumpMeta, JoinIntent, UnnestCteIntent, CteQueryIntent, CompileOptions, CompiledQuery, MutationIntent as MutationIntent$1, UpsertConflictTarget, UpsertConflictAction, RawCteIntent, TruncateOptions, VacuumOptions, AlterColumnOptions, CreateIndexOptions, DropIndexOptions, IndexInfo, ExpressionIntent, WhereExpressionIntent, AggOrderByArg, RelationType as RelationType$1, Cardinality, FeatureWarning, UnsupportedFeatureBehavior, FeatureBehaviorConfig, RangeValue, WhereRangeIntent, SubqueryIncludeInfo } from '@dbsp/types';
3
3
  export { Adapter, AdapterCapabilities, AdapterLogger, AdapterStreamOptions, AdjacencyTraversal, AggregateExpressionIntent, AggregateFunction, AggregateIntent, AggregateWindowFunction, AliasIncludedColumnsMode, AlterColumnOptions, AmbiguityCheckResult, ArrayOperator, BaseAdapter, CTEDefinition, Cardinality, CaseExpressionIntent, CoalesceExpressionIntent, ColumnAliasIntent, ColumnExpressionIntent, ColumnIR, ColumnType, CommonColumnType, ComparisonExpressionIntent, ComparisonOperator, CompileOnlyAdapter, CompileOptions, CompileResultWithIncludes, CompiledQuery, CompilingAdapter, CreateIndexOptions, CustomTraversal, DDLGeneratingAdapter, DbCasing, DecisionType, DeleteIntent, DialectCapabilities, DialectName, DropIndexOptions, DuckDBColumnType, Dump, DumpMeta, EdgeTableTraversal, EmitJoinClause, ExecutingAdapter, ExpressionIntent, FilterStrategy, ForeignKeyIR, IncludeIntent, IncludeStrategy, IndexColumnDef, IndexIR, IndexInfo, IndexMethod, InsertFromIntent, InsertIntent, IntrospectingAdapter, IsTypeSupported, JoinDefault, JoinIntent, LiteralExpressionIntent, LogicalOperator, MSSQLColumnType, ModelIR, MutationIntent, MySQLColumnType, NullOperator, NullsPosition, OffsetWindowFunction, OnDeleteAction, Optionality, OrderByIntent, PartitionIR, PlanDecision, PlanOptions, PlanReport, PlanWarning, PlanWarningCode, PostgresColumnType, PostgresOnlyColumnType, PseudoColumnExpressionIntent, PseudoColumnMetadata, PseudoColumnTraversal, QueryIntent, RangeOperator, RangeValue, RankingWindowFunction, RawExpressionIntent, RawSqlAdapter, RecursiveAdvancedOptions, RecursiveDedupe, RecursiveDirection, RecursiveEmitOptions, RecursiveExistsOptions, RecursiveIntent, RecursiveMetadata, RecursiveNodeIdExpr, RecursivePlanOptions, RecursivePlanReport, RecursiveTrackOptions, RecursiveTraversal, RelationColumnIntent, RelationIR, RelationKind, RelationOperator, RelationType, ResolvedIncludeStrategy, SQLiteColumnType, ScalarSubqueryIntent, SelectAggregateIntent, SelectAllIntent, SelectFieldsIntent, SelectIntent, SelectWithExpressionsIntent, SortDirection, StreamingAdapter, StringOperator, SubqueryIncludeInfo, SubqueryRefIntent, SupportedColumnTypes, TableIR, TransactionalAdapter, TruncateOptions, UpdateIntent, UpsertConflictAction, UpsertConflictTarget, UpsertFromIntent, UpsertIntent, VacuumOptions, WhereAndIntent, WhereAnyIntent, WhereComparisonIntent, WhereExistsIntent, WhereInIntent, WhereIntent, WhereLikeIntent, WhereNotExistsIntent, WhereNotIntent, WhereNullIntent, WhereOrIntent, WhereRangeIntent, WhereRelationFilterIntent, WhereSubqueryIntent, WindowFunction, WindowIntent, WindowOrderBy, getNodeIdAlias, isAdjacencyTraversal, isAggregateWindowFunction, isCoalesceExpression, isColumnAliasExpression, isCustomTraversal, isDeleteIntent, isEdgeTableTraversal, isInsertIntent, isMutationIntent, isRankingWindowFunction, isRawExpression, isRecursiveIntent, isRelationColumnExpression, isSelectAggregate, isSelectAll, isSelectFields, isSelectWithExpressions, isSubqueryRef, isUpdateIntent, isUpsertIntent, isWhereAnd, isWhereAny, isWhereComparison, isWhereExists, isWhereIn, isWhereLike, isWhereLogical, isWhereNot, isWhereNotExists, isWhereNull, isWhereOr, isWhereRange, isWhereRelationBased, isWhereRelationFilter, isWhereSubquery, isWindowIntent } from '@dbsp/types';
4
4
  import { NqlCompilerOptions } from '@dbsp/nql';
5
5
  import * as v from 'valibot';
@@ -3153,9 +3153,9 @@ type QueryHookContext = {
3153
3153
  };
3154
3154
  /** Union of all mutation intent types */
3155
3155
  /** Union of all mutation intent types */
3156
- type MutationIntent = InsertIntent | UpdateIntent | BatchUpdateIntent | DeleteIntent | UpsertIntent;
3156
+ type MutationIntent = InsertIntent | InsertFromIntent | UpdateIntent | BatchUpdateIntent | DeleteIntent | UpsertIntent | UpsertFromIntent;
3157
3157
  /** Mutation operation types */
3158
- type MutationOperation = 'insert' | 'update' | 'batchUpdate' | 'delete' | 'upsert';
3158
+ type MutationOperation = 'insert' | 'insert_from' | 'update' | 'batchUpdate' | 'delete' | 'upsert' | 'upsert_from';
3159
3159
  /**
3160
3160
  * Context passed to mutation hooks (beforeMutation/afterMutation).
3161
3161
  * Frozen via Object.freeze — hooks must return new objects to modify.
@@ -4635,12 +4635,9 @@ interface MutationDump {
4635
4635
  /** Bound parameter values */
4636
4636
  readonly parameters: readonly unknown[];
4637
4637
  /** The mutation intent */
4638
- readonly intent: InsertIntent | UpdateIntent | BatchUpdateIntent | DeleteIntent | UpsertIntent;
4638
+ readonly intent: MutationIntent$1;
4639
4639
  /** Optional metadata */
4640
- readonly meta?: {
4641
- readonly schema?: string;
4642
- readonly compiledAt?: Date;
4643
- };
4640
+ readonly meta?: DumpMeta;
4644
4641
  }
4645
4642
  /** Shared base options for all mutation builders */
4646
4643
  type MutationBaseOpts = {
@@ -4684,11 +4681,7 @@ declare abstract class MutationBuilderBase<T, TIntent extends InsertIntent | Upd
4684
4681
  protected requireAdapter(operation: string): Adapter;
4685
4682
  dump(extraOptions?: CompileOptions): MutationDump;
4686
4683
  execute(): Promise<T>;
4687
- private executeWithoutHooks;
4688
- private executeWithHooks;
4689
- private executeWithHooksInner;
4690
- /** Extract cardinality and data from mutation intent */
4691
- private extractIntentData;
4684
+ private prepareMutationExecution;
4692
4685
  }
4693
4686
  /**
4694
4687
  * Builder for INSERT operations.
@@ -4933,14 +4926,16 @@ declare class UpsertBuilder<T = void> extends MutationBuilderBase<T, UpsertInten
4933
4926
  interface NqlBuilder<T> {
4934
4927
  /** Execute query and return all results */
4935
4928
  all(): Promise<T[]>;
4929
+ /** Execute query or mutation and discard any returned rows */
4930
+ run(): Promise<void>;
4936
4931
  /** Execute query and return first result or null */
4937
4932
  first(): Promise<T | null>;
4938
4933
  /** Get the IntentIR for debugging */
4939
- toIntentIR(): QueryIntent;
4934
+ toIntentIR(): QueryIntent | MutationIntent$1;
4940
4935
  /** Get the execution plan */
4941
4936
  plan(): PlanReport;
4942
- /** Get full dump (plan + SQL + params) */
4943
- dump(meta?: DumpMetaInput): Dump;
4937
+ /** Get full dump. Mutations return MutationDump without a plan. */
4938
+ dump(meta?: DumpMetaInput): Dump | MutationDump;
4944
4939
  }
4945
4940
  /**
4946
4941
  * NQL template tag function type.
@@ -4984,7 +4979,7 @@ declare function nqlRaw(fragment: string): NqlRawFragment;
4984
4979
  * @param schemaName - Optional schema name for multi-tenant queries
4985
4980
  * @returns NQL template tag function
4986
4981
  */
4987
- declare function createNqlTag(schemaDefinition: unknown, model: ModelIR, adapter?: Adapter<unknown>, schemaName?: string): NqlTag;
4982
+ declare function createNqlTag(schemaDefinition: unknown, model: ModelIR, adapter?: Adapter<unknown>, schemaName?: string, hookStore?: HookStore, onHookError?: HookErrorHandler, inTransaction?: boolean): NqlTag;
4988
4983
  /**
4989
4984
  * Extract pseudo-column keywords from model configuration.
4990
4985
  * Collects all configured roles and recursive keywords from all tables'
package/dist/index.js CHANGED
@@ -5453,6 +5453,122 @@ var LightweightModelIR = class {
5453
5453
  };
5454
5454
 
5455
5455
  // src/dx/mutation-builders.ts
5456
+ function compileMutationIntent(adapter, intent, options) {
5457
+ switch (intent.type) {
5458
+ case "insert":
5459
+ return adapter.compileInsert(intent, options);
5460
+ case "insert_from":
5461
+ return adapter.compileInsertFrom(intent, options);
5462
+ case "update":
5463
+ return adapter.compileUpdate(intent, options);
5464
+ case "batchUpdate":
5465
+ return adapter.compileBatchUpdate(intent, options);
5466
+ case "delete":
5467
+ return adapter.compileDelete(intent, options);
5468
+ case "upsert":
5469
+ return adapter.compileUpsert(intent, options);
5470
+ case "upsert_from":
5471
+ return adapter.compileUpsertFrom(intent, options);
5472
+ }
5473
+ const _exhaustive = intent;
5474
+ throw new Error(
5475
+ `Unsupported mutation type: ${_exhaustive.type}`
5476
+ );
5477
+ }
5478
+ async function runMutationWithHooks(opts) {
5479
+ if (!opts.hookStore || !hasHooks(opts.hookStore)) {
5480
+ const prepared = opts.prepare(opts.intent);
5481
+ return prepared.execute();
5482
+ }
5483
+ return withReentrancyGuard(
5484
+ opts.hookStore,
5485
+ (store) => runMutationWithHooksInner(opts, store)
5486
+ );
5487
+ }
5488
+ async function runMutationWithHooksInner(opts, store) {
5489
+ const { intent } = opts;
5490
+ const operation = intent.type;
5491
+ const startTime = Date.now();
5492
+ const { cardinality, data } = extractMutationIntentData(intent);
5493
+ let ctx = Object.freeze({
5494
+ table: opts.table,
5495
+ operation,
5496
+ intent,
5497
+ cardinality,
5498
+ data,
5499
+ ...opts.schemaName !== void 0 ? { schemaName: opts.schemaName } : {},
5500
+ ...opts.inTransaction ? { inTransaction: true } : {}
5501
+ });
5502
+ try {
5503
+ if (store.beforeMutation.length > 0) {
5504
+ ctx = await runBeforeMutationHooks(
5505
+ store.beforeMutation,
5506
+ ctx,
5507
+ opts.onHookError
5508
+ );
5509
+ }
5510
+ const prepared = opts.prepare(intent);
5511
+ const duration = Date.now() - startTime;
5512
+ const result = await prepared.execute();
5513
+ if (store.afterMutation.length > 0) {
5514
+ const afterCtx = Object.freeze({
5515
+ ...ctx,
5516
+ sql: prepared.sql,
5517
+ parameters: prepared.parameters,
5518
+ duration
5519
+ });
5520
+ const transformed = await runAfterMutationHooks(
5521
+ store.afterMutation,
5522
+ afterCtx,
5523
+ [...prepared.getAfterMutationResult?.(result) ?? []],
5524
+ opts.onHookError
5525
+ );
5526
+ if (prepared.returnAfterMutationResult) {
5527
+ return transformed;
5528
+ }
5529
+ }
5530
+ return result;
5531
+ } catch (error) {
5532
+ if (store.onError.length > 0) {
5533
+ const errorCtx = {
5534
+ table: opts.table,
5535
+ operation,
5536
+ error,
5537
+ intent,
5538
+ phase: "beforeMutation",
5539
+ ...opts.schemaName !== void 0 ? { schemaName: opts.schemaName } : {}
5540
+ };
5541
+ const transformed = await runOnErrorHooks(store.onError, errorCtx);
5542
+ throw transformed;
5543
+ }
5544
+ throw error;
5545
+ }
5546
+ }
5547
+ function extractMutationIntentData(intent) {
5548
+ if (intent.type === "insert" || intent.type === "upsert") {
5549
+ const values = intent.values;
5550
+ return {
5551
+ cardinality: values.length > 1 ? "bulk" : "single",
5552
+ data: values.length > 1 ? values : values[0]
5553
+ };
5554
+ }
5555
+ if (intent.type === "update") {
5556
+ return {
5557
+ cardinality: "single",
5558
+ data: intent.set
5559
+ };
5560
+ }
5561
+ if (intent.type === "batchUpdate") {
5562
+ return {
5563
+ cardinality: "bulk",
5564
+ data: intent.updates
5565
+ };
5566
+ }
5567
+ if (intent.type === "insert_from" || intent.type === "upsert_from") {
5568
+ return { cardinality: "bulk", data: void 0 };
5569
+ }
5570
+ return { cardinality: "single", data: void 0 };
5571
+ }
5456
5572
  var MutationBuilderBase = class {
5457
5573
  table;
5458
5574
  model;
@@ -5507,11 +5623,15 @@ var MutationBuilderBase = class {
5507
5623
  Object.keys(compileOptions).length > 0 ? compileOptions : void 0
5508
5624
  );
5509
5625
  const meta = {
5510
- compiledAt: /* @__PURE__ */ new Date()
5626
+ compiledAt: /* @__PURE__ */ new Date(),
5627
+ ...this.schemaName !== void 0 && { schema: this.schemaName },
5628
+ ...extraOptions?.queryName !== void 0 && {
5629
+ queryName: extraOptions.queryName
5630
+ },
5631
+ ...extraOptions?.correlationId !== void 0 && {
5632
+ correlationId: extraOptions.correlationId
5633
+ }
5511
5634
  };
5512
- if (this.schemaName !== void 0) {
5513
- meta.schema = this.schemaName;
5514
- }
5515
5635
  return {
5516
5636
  sql: compiled.sql,
5517
5637
  parameters: compiled.parameters,
@@ -5521,129 +5641,38 @@ var MutationBuilderBase = class {
5521
5641
  }
5522
5642
  async execute() {
5523
5643
  const adapter = this.requireAdapter(this.operationName);
5524
- if (!this.hookStore || !hasHooks(this.hookStore)) {
5525
- return this.executeWithoutHooks(adapter);
5526
- }
5527
- return this.executeWithHooks(adapter);
5528
- }
5529
- async executeWithoutHooks(adapter) {
5530
5644
  const intent = this.buildIntent();
5531
- const compileOptions = this.schemaName ? { schemaName: this.schemaName } : void 0;
5532
- const compiled = this.compileIntent(adapter, intent, compileOptions);
5533
- if (this.returningColumns && this.returningColumns.length > 0) {
5534
- const result = await adapter.execute(compiled);
5535
- return result;
5536
- }
5537
- await adapter.execute(compiled);
5538
- return void 0;
5539
- }
5540
- async executeWithHooks(adapter) {
5541
- const store = this.hookStore;
5542
- if (!store) throw new Error("executeWithHooks called without hookStore");
5543
- return withReentrancyGuard(
5544
- store,
5545
- (s) => this.executeWithHooksInner(adapter, s)
5546
- );
5547
- }
5548
- async executeWithHooksInner(adapter, store) {
5549
- const intent = this.buildIntent();
5550
- const operation = intent.type;
5551
- const startTime = Date.now();
5552
- const { cardinality, data } = this.extractIntentData(intent);
5553
- let ctx = Object.freeze({
5645
+ return runMutationWithHooks({
5554
5646
  table: this.table,
5555
- operation,
5556
5647
  intent,
5557
- cardinality,
5558
- data,
5559
- ...this.schemaName !== void 0 ? { schemaName: this.schemaName } : {},
5560
- ...this.inTransaction ? { inTransaction: true } : {}
5648
+ hookStore: this.hookStore,
5649
+ onHookError: this.onHookError,
5650
+ schemaName: this.schemaName,
5651
+ inTransaction: this.inTransaction,
5652
+ prepare: (preparedIntent) => this.prepareMutationExecution(adapter, preparedIntent)
5561
5653
  });
5562
- try {
5563
- if (store.beforeMutation.length > 0) {
5564
- ctx = await runBeforeMutationHooks(
5565
- store.beforeMutation,
5566
- ctx,
5567
- this.onHookError
5568
- );
5569
- }
5570
- const compileOptions = this.schemaName ? { schemaName: this.schemaName } : void 0;
5571
- const compiled = this.compileIntent(adapter, intent, compileOptions);
5572
- const duration = Date.now() - startTime;
5573
- if (this.returningColumns && this.returningColumns.length > 0) {
5574
- const result = await adapter.execute(compiled);
5575
- const afterCtx = Object.freeze({
5576
- ...ctx,
5577
- sql: compiled.sql,
5578
- parameters: compiled.parameters,
5579
- duration
5580
- });
5581
- if (store.afterMutation.length > 0) {
5582
- const transformed = await runAfterMutationHooks(
5583
- store.afterMutation,
5584
- afterCtx,
5585
- result,
5586
- this.onHookError
5587
- );
5588
- return transformed;
5589
- }
5590
- return result;
5591
- }
5592
- await adapter.execute(compiled);
5593
- if (store.afterMutation.length > 0) {
5594
- const afterCtx = Object.freeze({
5595
- ...ctx,
5596
- sql: compiled.sql,
5597
- parameters: compiled.parameters,
5598
- duration
5599
- });
5600
- await runAfterMutationHooks(
5601
- store.afterMutation,
5602
- afterCtx,
5603
- [],
5604
- this.onHookError
5605
- );
5606
- }
5607
- return void 0;
5608
- } catch (error) {
5609
- if (store.onError.length > 0) {
5610
- const errorCtx = {
5611
- table: this.table,
5612
- operation,
5613
- error,
5614
- intent,
5615
- phase: "beforeMutation",
5616
- ...this.schemaName !== void 0 ? { schemaName: this.schemaName } : {}
5617
- };
5618
- const transformed = await runOnErrorHooks(store.onError, errorCtx);
5619
- throw transformed;
5620
- }
5621
- throw error;
5622
- }
5623
5654
  }
5624
- /** Extract cardinality and data from mutation intent */
5625
- extractIntentData(intent) {
5626
- if (intent.type === "insert" || intent.type === "upsert") {
5627
- const values = intent.values;
5628
- return {
5629
- cardinality: values.length > 1 ? "bulk" : "single",
5630
- data: values.length > 1 ? values : values[0]
5631
- };
5632
- }
5633
- if (intent.type === "update") {
5634
- return {
5635
- cardinality: "single",
5636
- data: intent.set
5637
- };
5638
- }
5639
- if (intent.type === "batchUpdate") {
5640
- const updates = intent.updates;
5655
+ prepareMutationExecution(adapter, intent) {
5656
+ const compileOptions = this.schemaName ? { schemaName: this.schemaName } : void 0;
5657
+ const compiled = this.compileIntent(adapter, intent, compileOptions);
5658
+ if (this.returningColumns && this.returningColumns.length > 0) {
5641
5659
  return {
5642
- cardinality: "bulk",
5643
- data: updates
5660
+ sql: compiled.sql,
5661
+ parameters: compiled.parameters,
5662
+ execute: () => adapter.execute(compiled),
5663
+ getAfterMutationResult: (result) => result,
5664
+ returnAfterMutationResult: true
5644
5665
  };
5645
5666
  }
5646
- return { cardinality: "single", data: void 0 };
5667
+ return {
5668
+ sql: compiled.sql,
5669
+ parameters: compiled.parameters,
5670
+ execute: async () => {
5671
+ await adapter.execute(compiled);
5672
+ return void 0;
5673
+ },
5674
+ getAfterMutationResult: () => []
5675
+ };
5647
5676
  }
5648
5677
  };
5649
5678
  var InsertBuilder = class _InsertBuilder extends MutationBuilderBase {
@@ -5703,7 +5732,7 @@ var InsertBuilder = class _InsertBuilder extends MutationBuilderBase {
5703
5732
  return intent;
5704
5733
  }
5705
5734
  compileIntent(adapter, intent, options) {
5706
- return adapter.compileInsert(intent, options);
5735
+ return compileMutationIntent(adapter, intent, options);
5707
5736
  }
5708
5737
  };
5709
5738
  var UpdateBuilder = class _UpdateBuilder extends MutationBuilderBase {
@@ -5858,10 +5887,7 @@ var UpdateBuilder = class _UpdateBuilder extends MutationBuilderBase {
5858
5887
  return intent;
5859
5888
  }
5860
5889
  compileIntent(adapter, intent, options) {
5861
- if (intent.type === "batchUpdate") {
5862
- return adapter.compileBatchUpdate(intent, options);
5863
- }
5864
- return adapter.compileUpdate(intent, options);
5890
+ return compileMutationIntent(adapter, intent, options);
5865
5891
  }
5866
5892
  };
5867
5893
  var DeleteBuilder = class _DeleteBuilder extends MutationBuilderBase {
@@ -5948,7 +5974,7 @@ var DeleteBuilder = class _DeleteBuilder extends MutationBuilderBase {
5948
5974
  return intent;
5949
5975
  }
5950
5976
  compileIntent(adapter, intent, options) {
5951
- return adapter.compileDelete(intent, options);
5977
+ return compileMutationIntent(adapter, intent, options);
5952
5978
  }
5953
5979
  };
5954
5980
  var UpsertBuilder = class _UpsertBuilder extends MutationBuilderBase {
@@ -6080,7 +6106,7 @@ var UpsertBuilder = class _UpsertBuilder extends MutationBuilderBase {
6080
6106
  return intent;
6081
6107
  }
6082
6108
  compileIntent(adapter, intent, options) {
6083
- return adapter.compileUpsert(intent, options);
6109
+ return compileMutationIntent(adapter, intent, options);
6084
6110
  }
6085
6111
  };
6086
6112
 
@@ -6139,6 +6165,19 @@ import {
6139
6165
  } from "@dbsp/nql";
6140
6166
  import { NQL_INTERNAL_COMPILER_OPTIONS } from "@dbsp/types/internal";
6141
6167
  var NQL_RAW_FRAGMENT = /* @__PURE__ */ Symbol("NqlRawFragment");
6168
+ function hasNqlBindings(bundle) {
6169
+ return (bundle.bindings?.size ?? 0) > 0;
6170
+ }
6171
+ function assertNoMutationBindingBodies(bundle) {
6172
+ const mutationBindings = bundle.mutationBindings;
6173
+ if (!mutationBindings || mutationBindings.size === 0) {
6174
+ return;
6175
+ }
6176
+ const bindNames = [...mutationBindings.keys()].map((name) => `'${name}'`).join(", ");
6177
+ throw new Error(
6178
+ `NQL tag programs cannot use mutation bodies in | bind clauses (${bindNames}); writable binding CTEs are disabled for tag execution (#173). Use a read-only bind before a single final mutation, or execute the mutation explicitly.`
6179
+ );
6180
+ }
6142
6181
  function nqlRaw(fragment) {
6143
6182
  if (typeof fragment !== "string") {
6144
6183
  throw new TypeError("nqlRaw() expects a string fragment");
@@ -6250,7 +6289,7 @@ function assembleNqlTemplate(strings, values) {
6250
6289
  sourceError: findInternalParamSourceError(query, generatedRanges)
6251
6290
  };
6252
6291
  }
6253
- function createNqlTag(schemaDefinition, model, adapter, schemaName) {
6292
+ function createNqlTag(schemaDefinition, model, adapter, schemaName, hookStore, onHookError, inTransaction) {
6254
6293
  return function nql(strings, ...values) {
6255
6294
  const assembled = assembleNqlTemplate(strings, values);
6256
6295
  return new NqlBuilderImpl(
@@ -6261,22 +6300,27 @@ function createNqlTag(schemaDefinition, model, adapter, schemaName) {
6261
6300
  schemaDefinition,
6262
6301
  model,
6263
6302
  adapter,
6264
- schemaName
6303
+ schemaName,
6304
+ hookStore,
6305
+ onHookError,
6306
+ inTransaction
6265
6307
  );
6266
6308
  };
6267
6309
  }
6268
6310
  var NqlBuilderImpl = class {
6269
- _intent;
6311
+ _compiled;
6270
6312
  query;
6271
6313
  params;
6272
6314
  hasBoundParams;
6273
6315
  sourceError;
6274
6316
  schemaDefinition;
6275
6317
  model;
6276
- // biome-ignore lint/correctness/noUnusedPrivateClassMembers: Reserved for future schema-scoping support
6277
6318
  _schemaName;
6278
6319
  adapter;
6279
- constructor(query, params, hasBoundParams, sourceError, schemaDefinition, model, adapter, schemaName) {
6320
+ hookStore;
6321
+ onHookError;
6322
+ inTransaction;
6323
+ constructor(query, params, hasBoundParams, sourceError, schemaDefinition, model, adapter, schemaName, hookStore, onHookError, inTransaction) {
6280
6324
  this.query = query;
6281
6325
  this.params = params;
6282
6326
  this.hasBoundParams = hasBoundParams;
@@ -6285,10 +6329,13 @@ var NqlBuilderImpl = class {
6285
6329
  this.model = model;
6286
6330
  this.adapter = adapter;
6287
6331
  this._schemaName = schemaName;
6332
+ this.hookStore = hookStore;
6333
+ this.onHookError = onHookError;
6334
+ this.inTransaction = inTransaction;
6288
6335
  }
6289
6336
  compile() {
6290
- if (this._intent) {
6291
- return this._intent;
6337
+ if (this._compiled) {
6338
+ return this._compiled;
6292
6339
  }
6293
6340
  if (this.sourceError !== void 0) {
6294
6341
  throw new Error(`NQL compilation failed: ${this.sourceError}`);
@@ -6309,28 +6356,53 @@ var NqlBuilderImpl = class {
6309
6356
  const rawHint = this.hasBoundParams ? " If an interpolation was intended as NQL structure, wrap a trusted fragment with nqlRaw()." : "";
6310
6357
  throw new Error(`NQL compilation failed: ${errors}${rawHint}`);
6311
6358
  }
6312
- if (result.ast?.mutation && !result.ast?.query) {
6313
- throw new Error(
6314
- "INSERT/UPDATE/DELETE/UPSERT not yet supported via the nql`...` tagged template. Use orm.insert(table, data) / orm.update(table, set) / orm.delete(table) / orm.upsert(table, data) instead. Tracking: https://github.com/oorabona/db-semantic-planner/issues/113"
6315
- );
6316
- }
6317
- if (!result.ast?.query) {
6359
+ const bundle = result.ast;
6360
+ if (!bundle) {
6318
6361
  throw new Error("NQL compilation failed: no query AST produced");
6319
6362
  }
6320
- this._intent = result.ast.query;
6321
- return this._intent;
6363
+ assertNoMutationBindingBodies(bundle);
6364
+ if (bundle.query) {
6365
+ this._compiled = {
6366
+ kind: "query",
6367
+ bundle,
6368
+ intent: bundle.query
6369
+ };
6370
+ return this._compiled;
6371
+ }
6372
+ if (bundle.mutation) {
6373
+ this._compiled = {
6374
+ kind: "mutation",
6375
+ bundle,
6376
+ intent: bundle.mutation
6377
+ };
6378
+ return this._compiled;
6379
+ }
6380
+ throw new Error("NQL compilation failed: no query AST produced");
6322
6381
  }
6323
6382
  toIntentIR() {
6324
- return this.compile();
6383
+ return this.compile().intent;
6325
6384
  }
6326
6385
  planInternal() {
6327
- const intent = this.compile();
6328
- return plan(intent, this.model);
6386
+ const compiled = this.compile();
6387
+ if (compiled.kind === "mutation") {
6388
+ throw new Error(
6389
+ "NQL mutations do not have execution plans; use dump() for SQL and parameters."
6390
+ );
6391
+ }
6392
+ return plan(compiled.intent, this.model);
6329
6393
  }
6330
6394
  plan() {
6331
6395
  return this.planInternal();
6332
6396
  }
6333
6397
  dump(meta) {
6398
+ const compiledIntent = this.compile();
6399
+ if (compiledIntent.kind === "mutation") {
6400
+ return this.dumpMutation(
6401
+ compiledIntent.bundle,
6402
+ compiledIntent.intent,
6403
+ meta
6404
+ );
6405
+ }
6334
6406
  const planReport = this.planInternal();
6335
6407
  if (!this.adapter) {
6336
6408
  return {
@@ -6340,7 +6412,10 @@ var NqlBuilderImpl = class {
6340
6412
  ...meta !== void 0 && { meta }
6341
6413
  };
6342
6414
  }
6343
- const compiled = this.adapter.compile(planReport);
6415
+ const compiled = hasNqlBindings(compiledIntent.bundle) ? this.adapter.compile(
6416
+ compiledIntent.bundle,
6417
+ this.nqlBundleCompileOptions()
6418
+ ) : this.adapter.compile(planReport);
6344
6419
  try {
6345
6420
  return this.adapter.createDump(planReport, compiled, meta);
6346
6421
  } catch (err) {
@@ -6368,15 +6443,88 @@ var NqlBuilderImpl = class {
6368
6443
  throw err;
6369
6444
  }
6370
6445
  }
6371
- async all() {
6446
+ requireAdapter(operation) {
6372
6447
  if (!this.adapter) {
6448
+ throw new ExecutionError({
6449
+ operation,
6450
+ reason: "Adapter not configured",
6451
+ fix: "Pass adapter option when creating ORM: createOrm({ model, adapter })"
6452
+ });
6453
+ }
6454
+ return this.adapter;
6455
+ }
6456
+ mutationCompileOptions(extraOptions) {
6457
+ return {
6458
+ model: this.model,
6459
+ ...this._schemaName !== void 0 && { schemaName: this._schemaName },
6460
+ ...extraOptions
6461
+ };
6462
+ }
6463
+ nqlBundleCompileOptions() {
6464
+ return {
6465
+ model: this.model,
6466
+ ...this._schemaName !== void 0 && { schemaName: this._schemaName }
6467
+ };
6468
+ }
6469
+ dumpMutation(bundle, intent, meta) {
6470
+ const adapter = this.requireAdapter("dump");
6471
+ const compiled = adapter.compile(bundle, this.mutationCompileOptions(meta));
6472
+ const dumpMeta = {
6473
+ compiledAt: /* @__PURE__ */ new Date(),
6474
+ ...this._schemaName !== void 0 && { schema: this._schemaName },
6475
+ ...meta?.queryName !== void 0 && { queryName: meta.queryName },
6476
+ ...meta?.correlationId !== void 0 && {
6477
+ correlationId: meta.correlationId
6478
+ }
6479
+ };
6480
+ return {
6481
+ sql: compiled.sql,
6482
+ parameters: compiled.parameters,
6483
+ intent,
6484
+ meta: dumpMeta
6485
+ };
6486
+ }
6487
+ async all() {
6488
+ const adapter = this.adapter;
6489
+ if (!adapter) {
6373
6490
  throw new Error(
6374
6491
  "Cannot execute query: no adapter configured. Pass an adapter to createOrm() or use .toIntentIR() / .plan() for debugging."
6375
6492
  );
6376
6493
  }
6494
+ const compiledIntent = this.compile();
6495
+ if (compiledIntent.kind === "mutation") {
6496
+ const hasReturning = (compiledIntent.intent.returning?.length ?? 0) > 0;
6497
+ return runMutationWithHooks({
6498
+ table: compiledIntent.intent.table,
6499
+ intent: compiledIntent.intent,
6500
+ hookStore: this.hookStore,
6501
+ onHookError: this.onHookError,
6502
+ schemaName: this._schemaName,
6503
+ inTransaction: this.inTransaction,
6504
+ prepare: () => {
6505
+ const compiled2 = adapter.compile(
6506
+ compiledIntent.bundle,
6507
+ this.mutationCompileOptions()
6508
+ );
6509
+ return {
6510
+ sql: compiled2.sql,
6511
+ parameters: compiled2.parameters,
6512
+ execute: () => adapter.execute(compiled2),
6513
+ getAfterMutationResult: (result) => hasReturning ? result : [],
6514
+ returnAfterMutationResult: hasReturning
6515
+ };
6516
+ }
6517
+ });
6518
+ }
6377
6519
  const planReport = this.planInternal();
6378
- const compiled = this.adapter.compile(planReport);
6379
- return this.adapter.execute(compiled);
6520
+ const compiled = hasNqlBindings(compiledIntent.bundle) ? adapter.compile(
6521
+ compiledIntent.bundle,
6522
+ this.nqlBundleCompileOptions()
6523
+ ) : adapter.compile(planReport);
6524
+ return adapter.execute(compiled);
6525
+ }
6526
+ async run() {
6527
+ await this.all();
6380
6528
  }
6381
6529
  async first() {
6382
6530
  const rows = await this.all();
@@ -9151,7 +9299,10 @@ function createOrmInstance(model, strictMode, relationHints, adapter, schemaName
9151
9299
  schemaDefinition,
9152
9300
  model,
9153
9301
  adapter,
9154
- schemaName
9302
+ schemaName,
9303
+ hookStore,
9304
+ onHookError,
9305
+ inTransaction
9155
9306
  );
9156
9307
  const mutationOpts = {
9157
9308
  model,