arkormx 0.1.11 → 0.2.1

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.cjs CHANGED
@@ -2203,9 +2203,14 @@ const defineFactory = (model, definition) => {
2203
2203
  * @since 0.1.0
2204
2204
  */
2205
2205
  var ModelNotFoundException = class extends ArkormException {
2206
- constructor(message = "No query results for the given model.") {
2206
+ modelName;
2207
+ constructor(modelName, message = "No query results for the given model.") {
2207
2208
  super(message);
2208
2209
  this.name = "ModelNotFoundException";
2210
+ this.modelName = modelName;
2211
+ }
2212
+ getModelName() {
2213
+ return this.modelName;
2209
2214
  }
2210
2215
  };
2211
2216
 
@@ -3605,7 +3610,7 @@ var QueryBuilder = class QueryBuilder {
3605
3610
  */
3606
3611
  async firstOrFail() {
3607
3612
  const model = await this.first();
3608
- if (!model) throw new ModelNotFoundException("Record not found.");
3613
+ if (!model) throw new ModelNotFoundException(this.model.name, "Record not found.");
3609
3614
  return model;
3610
3615
  }
3611
3616
  async find(value, key = "id") {
@@ -3638,7 +3643,7 @@ var QueryBuilder = class QueryBuilder {
3638
3643
  */
3639
3644
  async valueOrFail(column) {
3640
3645
  const result = await this.value(column);
3641
- if (result == null) throw new ModelNotFoundException("Record not found.");
3646
+ if (result == null) throw new ModelNotFoundException(this.model.name, "Record not found.");
3642
3647
  return result;
3643
3648
  }
3644
3649
  /**
@@ -3664,6 +3669,99 @@ var QueryBuilder = class QueryBuilder {
3664
3669
  return this.model.hydrate(created);
3665
3670
  }
3666
3671
  /**
3672
+ * Creates multiple records and returns hydrated model instances.
3673
+ *
3674
+ * @param values
3675
+ * @returns
3676
+ */
3677
+ async createMany(values) {
3678
+ if (values.length === 0) return [];
3679
+ return await Promise.all(values.map(async (value) => await this.create(value)));
3680
+ }
3681
+ /**
3682
+ * Insert one or more records.
3683
+ *
3684
+ * @param values
3685
+ * @returns
3686
+ */
3687
+ async insert(values) {
3688
+ const payloads = this.normalizeInsertPayloads(values);
3689
+ if (payloads.length === 0) return true;
3690
+ const delegate = this.delegate;
3691
+ if (typeof delegate.createMany === "function") {
3692
+ await delegate.createMany({ data: payloads });
3693
+ return true;
3694
+ }
3695
+ await Promise.all(payloads.map(async (payload) => {
3696
+ await this.delegate.create({ data: payload });
3697
+ }));
3698
+ return true;
3699
+ }
3700
+ /**
3701
+ * Insert one or more records while ignoring insertion errors.
3702
+ *
3703
+ * @param values
3704
+ * @returns
3705
+ */
3706
+ async insertOrIgnore(values) {
3707
+ const payloads = this.normalizeInsertPayloads(values);
3708
+ if (payloads.length === 0) return 0;
3709
+ const delegate = this.delegate;
3710
+ if (typeof delegate.createMany === "function") {
3711
+ const result = await delegate.createMany({
3712
+ data: payloads,
3713
+ skipDuplicates: true
3714
+ });
3715
+ return this.resolveAffectedCount(result, payloads.length);
3716
+ }
3717
+ let inserted = 0;
3718
+ for (const payload of payloads) try {
3719
+ await this.delegate.create({ data: payload });
3720
+ inserted += 1;
3721
+ } catch {
3722
+ continue;
3723
+ }
3724
+ return inserted;
3725
+ }
3726
+ /**
3727
+ * Insert a record and return its primary key value.
3728
+ *
3729
+ * @param values
3730
+ * @param sequence
3731
+ * @returns
3732
+ */
3733
+ async insertGetId(values, sequence) {
3734
+ const created = await this.delegate.create({ data: values });
3735
+ const key = sequence ?? "id";
3736
+ if (!(key in created)) throw new ArkormException(`Inserted record does not contain key [${key}].`);
3737
+ return created[key];
3738
+ }
3739
+ /**
3740
+ * Insert records using values produced by another query/source.
3741
+ *
3742
+ * @param columns
3743
+ * @param query
3744
+ * @returns
3745
+ */
3746
+ async insertUsing(columns, query) {
3747
+ const rows = await this.resolveInsertUsingRows(columns, query);
3748
+ if (rows.length === 0) return 0;
3749
+ await this.insert(rows);
3750
+ return rows.length;
3751
+ }
3752
+ /**
3753
+ * Insert records using values produced by another query/source while ignoring insertion errors.
3754
+ *
3755
+ * @param columns
3756
+ * @param query
3757
+ * @returns
3758
+ */
3759
+ async insertOrIgnoreUsing(columns, query) {
3760
+ const rows = await this.resolveInsertUsingRows(columns, query);
3761
+ if (rows.length === 0) return 0;
3762
+ return this.insertOrIgnore(rows);
3763
+ }
3764
+ /**
3667
3765
  * Updates records matching the current query constraints with the
3668
3766
  * specified data and returns the updated record(s) as model instance(s).
3669
3767
  *
@@ -3681,6 +3779,71 @@ var QueryBuilder = class QueryBuilder {
3681
3779
  return this.model.hydrate(updated);
3682
3780
  }
3683
3781
  /**
3782
+ * Update records using update-many semantics when available.
3783
+ *
3784
+ * @param data
3785
+ * @returns
3786
+ */
3787
+ async updateFrom(data) {
3788
+ const where = this.buildWhere();
3789
+ if (!where) throw new ArkormException("Update requires a where clause.");
3790
+ const delegate = this.delegate;
3791
+ if (typeof delegate.updateMany === "function") {
3792
+ const result = await delegate.updateMany({
3793
+ where,
3794
+ data
3795
+ });
3796
+ return this.resolveAffectedCount(result, 0);
3797
+ }
3798
+ await this.update(data);
3799
+ return 1;
3800
+ }
3801
+ /**
3802
+ * Insert a record when no match exists, otherwise update the matching record.
3803
+ *
3804
+ * @param attributes
3805
+ * @param values
3806
+ * @returns
3807
+ */
3808
+ async updateOrInsert(attributes, values = {}) {
3809
+ const exists = await this.delegate.findFirst({ where: attributes }) != null;
3810
+ const resolvedValues = typeof values === "function" ? await values(exists) : values;
3811
+ if (!exists) {
3812
+ await this.delegate.create({ data: {
3813
+ ...attributes,
3814
+ ...resolvedValues
3815
+ } });
3816
+ return true;
3817
+ }
3818
+ return await this.clone().where(attributes).update(resolvedValues) != null;
3819
+ }
3820
+ /**
3821
+ * Insert new records or update existing records by one or more unique keys.
3822
+ *
3823
+ * @param values
3824
+ * @param uniqueBy
3825
+ * @param update
3826
+ * @returns
3827
+ */
3828
+ async upsert(values, uniqueBy, update = null) {
3829
+ if (values.length === 0) return 0;
3830
+ const uniqueKeys = Array.isArray(uniqueBy) ? uniqueBy : [uniqueBy];
3831
+ let affected = 0;
3832
+ for (const row of values) {
3833
+ const attributes = uniqueKeys.reduce((all, key) => {
3834
+ all[key] = row[key];
3835
+ return all;
3836
+ }, {});
3837
+ const updatePayload = (update ?? Object.keys(row).filter((key) => !uniqueKeys.includes(key))).reduce((all, key) => {
3838
+ if (key in row) all[key] = row[key];
3839
+ return all;
3840
+ }, {});
3841
+ await this.updateOrInsert(attributes, updatePayload);
3842
+ affected += 1;
3843
+ }
3844
+ return affected;
3845
+ }
3846
+ /**
3684
3847
  * Deletes records matching the current query constraints and returns
3685
3848
  * the deleted record(s) as model instance(s).
3686
3849
  *
@@ -3719,6 +3882,43 @@ var QueryBuilder = class QueryBuilder {
3719
3882
  async doesntExist() {
3720
3883
  return !await this.exists();
3721
3884
  }
3885
+ normalizeInsertPayloads(values) {
3886
+ if (Array.isArray(values)) return values;
3887
+ return [values];
3888
+ }
3889
+ resolveAffectedCount(result, fallback) {
3890
+ if (typeof result === "number") return result;
3891
+ if (result && typeof result === "object" && "count" in result) {
3892
+ const candidate = result.count;
3893
+ if (typeof candidate === "number") return candidate;
3894
+ }
3895
+ return fallback;
3896
+ }
3897
+ async resolveInsertUsingRows(columns, query) {
3898
+ const resolvedQuery = typeof query === "function" ? await query() : query;
3899
+ return (await this.resolveInsertUsingSource(resolvedQuery)).map((row) => {
3900
+ return columns.reduce((record, column) => {
3901
+ record[column] = row[column];
3902
+ return record;
3903
+ }, {});
3904
+ });
3905
+ }
3906
+ async resolveInsertUsingSource(source) {
3907
+ if (source && typeof source === "object") {
3908
+ const asBuilder = source;
3909
+ if (typeof asBuilder.get === "function") {
3910
+ const collection = await asBuilder.get();
3911
+ if (typeof collection.all === "function") return collection.all().map((item) => {
3912
+ const asModel = item;
3913
+ if (typeof asModel.getRawAttributes === "function") return asModel.getRawAttributes();
3914
+ return item;
3915
+ });
3916
+ }
3917
+ if (Array.isArray(source)) return source;
3918
+ }
3919
+ if (Array.isArray(source)) return source;
3920
+ throw new ArkormException("insertUsing expects a query builder, array of records, or async resolver.");
3921
+ }
3722
3922
  /**
3723
3923
  * Execute callback when no records exist.
3724
3924
  *
package/dist/index.d.cts CHANGED
@@ -1477,6 +1477,51 @@ declare class QueryBuilder<TModel, TDelegate extends PrismaDelegateLike = Prisma
1477
1477
  * @returns
1478
1478
  */
1479
1479
  create(data: DelegateCreateData<TDelegate>): Promise<TModel>;
1480
+ /**
1481
+ * Creates multiple records and returns hydrated model instances.
1482
+ *
1483
+ * @param values
1484
+ * @returns
1485
+ */
1486
+ createMany(values: DelegateCreateData<TDelegate>[]): Promise<TModel[]>;
1487
+ /**
1488
+ * Insert one or more records.
1489
+ *
1490
+ * @param values
1491
+ * @returns
1492
+ */
1493
+ insert(values: DelegateCreateData<TDelegate> | DelegateCreateData<TDelegate>[]): Promise<boolean>;
1494
+ /**
1495
+ * Insert one or more records while ignoring insertion errors.
1496
+ *
1497
+ * @param values
1498
+ * @returns
1499
+ */
1500
+ insertOrIgnore(values: DelegateCreateData<TDelegate> | DelegateCreateData<TDelegate>[]): Promise<number>;
1501
+ /**
1502
+ * Insert a record and return its primary key value.
1503
+ *
1504
+ * @param values
1505
+ * @param sequence
1506
+ * @returns
1507
+ */
1508
+ insertGetId(values: DelegateCreateData<TDelegate>, sequence?: string | null): Promise<unknown>;
1509
+ /**
1510
+ * Insert records using values produced by another query/source.
1511
+ *
1512
+ * @param columns
1513
+ * @param query
1514
+ * @returns
1515
+ */
1516
+ insertUsing(columns: string[], query: unknown): Promise<number>;
1517
+ /**
1518
+ * Insert records using values produced by another query/source while ignoring insertion errors.
1519
+ *
1520
+ * @param columns
1521
+ * @param query
1522
+ * @returns
1523
+ */
1524
+ insertOrIgnoreUsing(columns: string[], query: unknown): Promise<number>;
1480
1525
  /**
1481
1526
  * Updates records matching the current query constraints with the
1482
1527
  * specified data and returns the updated record(s) as model instance(s).
@@ -1485,6 +1530,30 @@ declare class QueryBuilder<TModel, TDelegate extends PrismaDelegateLike = Prisma
1485
1530
  * @returns
1486
1531
  */
1487
1532
  update(data: DelegateUpdateData<TDelegate>): Promise<TModel>;
1533
+ /**
1534
+ * Update records using update-many semantics when available.
1535
+ *
1536
+ * @param data
1537
+ * @returns
1538
+ */
1539
+ updateFrom(data: DelegateUpdateData<TDelegate>): Promise<number>;
1540
+ /**
1541
+ * Insert a record when no match exists, otherwise update the matching record.
1542
+ *
1543
+ * @param attributes
1544
+ * @param values
1545
+ * @returns
1546
+ */
1547
+ updateOrInsert(attributes: Record<string, unknown>, values?: Record<string, unknown> | ((exists: boolean) => Record<string, unknown> | Promise<Record<string, unknown>>)): Promise<boolean>;
1548
+ /**
1549
+ * Insert new records or update existing records by one or more unique keys.
1550
+ *
1551
+ * @param values
1552
+ * @param uniqueBy
1553
+ * @param update
1554
+ * @returns
1555
+ */
1556
+ upsert(values: Array<Record<string, unknown>>, uniqueBy: string | string[], update?: string[] | null): Promise<number>;
1488
1557
  /**
1489
1558
  * Deletes records matching the current query constraints and returns
1490
1559
  * the deleted record(s) as model instance(s).
@@ -1510,6 +1579,10 @@ declare class QueryBuilder<TModel, TDelegate extends PrismaDelegateLike = Prisma
1510
1579
  * @returns
1511
1580
  */
1512
1581
  doesntExist(): Promise<boolean>;
1582
+ private normalizeInsertPayloads;
1583
+ private resolveAffectedCount;
1584
+ private resolveInsertUsingRows;
1585
+ private resolveInsertUsingSource;
1513
1586
  /**
1514
1587
  * Execute callback when no records exist.
1515
1588
  *
@@ -2557,7 +2630,9 @@ declare class ArkormException extends Error {
2557
2630
  * @since 0.1.0
2558
2631
  */
2559
2632
  declare class ModelNotFoundException extends ArkormException {
2560
- constructor(message?: string);
2633
+ private modelName;
2634
+ constructor(modelName: string, message?: string);
2635
+ getModelName(): string;
2561
2636
  }
2562
2637
  //#endregion
2563
2638
  //#region src/helpers/migrations.d.ts
package/dist/index.d.mts CHANGED
@@ -1477,6 +1477,51 @@ declare class QueryBuilder<TModel, TDelegate extends PrismaDelegateLike = Prisma
1477
1477
  * @returns
1478
1478
  */
1479
1479
  create(data: DelegateCreateData<TDelegate>): Promise<TModel>;
1480
+ /**
1481
+ * Creates multiple records and returns hydrated model instances.
1482
+ *
1483
+ * @param values
1484
+ * @returns
1485
+ */
1486
+ createMany(values: DelegateCreateData<TDelegate>[]): Promise<TModel[]>;
1487
+ /**
1488
+ * Insert one or more records.
1489
+ *
1490
+ * @param values
1491
+ * @returns
1492
+ */
1493
+ insert(values: DelegateCreateData<TDelegate> | DelegateCreateData<TDelegate>[]): Promise<boolean>;
1494
+ /**
1495
+ * Insert one or more records while ignoring insertion errors.
1496
+ *
1497
+ * @param values
1498
+ * @returns
1499
+ */
1500
+ insertOrIgnore(values: DelegateCreateData<TDelegate> | DelegateCreateData<TDelegate>[]): Promise<number>;
1501
+ /**
1502
+ * Insert a record and return its primary key value.
1503
+ *
1504
+ * @param values
1505
+ * @param sequence
1506
+ * @returns
1507
+ */
1508
+ insertGetId(values: DelegateCreateData<TDelegate>, sequence?: string | null): Promise<unknown>;
1509
+ /**
1510
+ * Insert records using values produced by another query/source.
1511
+ *
1512
+ * @param columns
1513
+ * @param query
1514
+ * @returns
1515
+ */
1516
+ insertUsing(columns: string[], query: unknown): Promise<number>;
1517
+ /**
1518
+ * Insert records using values produced by another query/source while ignoring insertion errors.
1519
+ *
1520
+ * @param columns
1521
+ * @param query
1522
+ * @returns
1523
+ */
1524
+ insertOrIgnoreUsing(columns: string[], query: unknown): Promise<number>;
1480
1525
  /**
1481
1526
  * Updates records matching the current query constraints with the
1482
1527
  * specified data and returns the updated record(s) as model instance(s).
@@ -1485,6 +1530,30 @@ declare class QueryBuilder<TModel, TDelegate extends PrismaDelegateLike = Prisma
1485
1530
  * @returns
1486
1531
  */
1487
1532
  update(data: DelegateUpdateData<TDelegate>): Promise<TModel>;
1533
+ /**
1534
+ * Update records using update-many semantics when available.
1535
+ *
1536
+ * @param data
1537
+ * @returns
1538
+ */
1539
+ updateFrom(data: DelegateUpdateData<TDelegate>): Promise<number>;
1540
+ /**
1541
+ * Insert a record when no match exists, otherwise update the matching record.
1542
+ *
1543
+ * @param attributes
1544
+ * @param values
1545
+ * @returns
1546
+ */
1547
+ updateOrInsert(attributes: Record<string, unknown>, values?: Record<string, unknown> | ((exists: boolean) => Record<string, unknown> | Promise<Record<string, unknown>>)): Promise<boolean>;
1548
+ /**
1549
+ * Insert new records or update existing records by one or more unique keys.
1550
+ *
1551
+ * @param values
1552
+ * @param uniqueBy
1553
+ * @param update
1554
+ * @returns
1555
+ */
1556
+ upsert(values: Array<Record<string, unknown>>, uniqueBy: string | string[], update?: string[] | null): Promise<number>;
1488
1557
  /**
1489
1558
  * Deletes records matching the current query constraints and returns
1490
1559
  * the deleted record(s) as model instance(s).
@@ -1510,6 +1579,10 @@ declare class QueryBuilder<TModel, TDelegate extends PrismaDelegateLike = Prisma
1510
1579
  * @returns
1511
1580
  */
1512
1581
  doesntExist(): Promise<boolean>;
1582
+ private normalizeInsertPayloads;
1583
+ private resolveAffectedCount;
1584
+ private resolveInsertUsingRows;
1585
+ private resolveInsertUsingSource;
1513
1586
  /**
1514
1587
  * Execute callback when no records exist.
1515
1588
  *
@@ -2557,7 +2630,9 @@ declare class ArkormException extends Error {
2557
2630
  * @since 0.1.0
2558
2631
  */
2559
2632
  declare class ModelNotFoundException extends ArkormException {
2560
- constructor(message?: string);
2633
+ private modelName;
2634
+ constructor(modelName: string, message?: string);
2635
+ getModelName(): string;
2561
2636
  }
2562
2637
  //#endregion
2563
2638
  //#region src/helpers/migrations.d.ts
package/dist/index.mjs CHANGED
@@ -2174,9 +2174,14 @@ const defineFactory = (model, definition) => {
2174
2174
  * @since 0.1.0
2175
2175
  */
2176
2176
  var ModelNotFoundException = class extends ArkormException {
2177
- constructor(message = "No query results for the given model.") {
2177
+ modelName;
2178
+ constructor(modelName, message = "No query results for the given model.") {
2178
2179
  super(message);
2179
2180
  this.name = "ModelNotFoundException";
2181
+ this.modelName = modelName;
2182
+ }
2183
+ getModelName() {
2184
+ return this.modelName;
2180
2185
  }
2181
2186
  };
2182
2187
 
@@ -3576,7 +3581,7 @@ var QueryBuilder = class QueryBuilder {
3576
3581
  */
3577
3582
  async firstOrFail() {
3578
3583
  const model = await this.first();
3579
- if (!model) throw new ModelNotFoundException("Record not found.");
3584
+ if (!model) throw new ModelNotFoundException(this.model.name, "Record not found.");
3580
3585
  return model;
3581
3586
  }
3582
3587
  async find(value, key = "id") {
@@ -3609,7 +3614,7 @@ var QueryBuilder = class QueryBuilder {
3609
3614
  */
3610
3615
  async valueOrFail(column) {
3611
3616
  const result = await this.value(column);
3612
- if (result == null) throw new ModelNotFoundException("Record not found.");
3617
+ if (result == null) throw new ModelNotFoundException(this.model.name, "Record not found.");
3613
3618
  return result;
3614
3619
  }
3615
3620
  /**
@@ -3635,6 +3640,99 @@ var QueryBuilder = class QueryBuilder {
3635
3640
  return this.model.hydrate(created);
3636
3641
  }
3637
3642
  /**
3643
+ * Creates multiple records and returns hydrated model instances.
3644
+ *
3645
+ * @param values
3646
+ * @returns
3647
+ */
3648
+ async createMany(values) {
3649
+ if (values.length === 0) return [];
3650
+ return await Promise.all(values.map(async (value) => await this.create(value)));
3651
+ }
3652
+ /**
3653
+ * Insert one or more records.
3654
+ *
3655
+ * @param values
3656
+ * @returns
3657
+ */
3658
+ async insert(values) {
3659
+ const payloads = this.normalizeInsertPayloads(values);
3660
+ if (payloads.length === 0) return true;
3661
+ const delegate = this.delegate;
3662
+ if (typeof delegate.createMany === "function") {
3663
+ await delegate.createMany({ data: payloads });
3664
+ return true;
3665
+ }
3666
+ await Promise.all(payloads.map(async (payload) => {
3667
+ await this.delegate.create({ data: payload });
3668
+ }));
3669
+ return true;
3670
+ }
3671
+ /**
3672
+ * Insert one or more records while ignoring insertion errors.
3673
+ *
3674
+ * @param values
3675
+ * @returns
3676
+ */
3677
+ async insertOrIgnore(values) {
3678
+ const payloads = this.normalizeInsertPayloads(values);
3679
+ if (payloads.length === 0) return 0;
3680
+ const delegate = this.delegate;
3681
+ if (typeof delegate.createMany === "function") {
3682
+ const result = await delegate.createMany({
3683
+ data: payloads,
3684
+ skipDuplicates: true
3685
+ });
3686
+ return this.resolveAffectedCount(result, payloads.length);
3687
+ }
3688
+ let inserted = 0;
3689
+ for (const payload of payloads) try {
3690
+ await this.delegate.create({ data: payload });
3691
+ inserted += 1;
3692
+ } catch {
3693
+ continue;
3694
+ }
3695
+ return inserted;
3696
+ }
3697
+ /**
3698
+ * Insert a record and return its primary key value.
3699
+ *
3700
+ * @param values
3701
+ * @param sequence
3702
+ * @returns
3703
+ */
3704
+ async insertGetId(values, sequence) {
3705
+ const created = await this.delegate.create({ data: values });
3706
+ const key = sequence ?? "id";
3707
+ if (!(key in created)) throw new ArkormException(`Inserted record does not contain key [${key}].`);
3708
+ return created[key];
3709
+ }
3710
+ /**
3711
+ * Insert records using values produced by another query/source.
3712
+ *
3713
+ * @param columns
3714
+ * @param query
3715
+ * @returns
3716
+ */
3717
+ async insertUsing(columns, query) {
3718
+ const rows = await this.resolveInsertUsingRows(columns, query);
3719
+ if (rows.length === 0) return 0;
3720
+ await this.insert(rows);
3721
+ return rows.length;
3722
+ }
3723
+ /**
3724
+ * Insert records using values produced by another query/source while ignoring insertion errors.
3725
+ *
3726
+ * @param columns
3727
+ * @param query
3728
+ * @returns
3729
+ */
3730
+ async insertOrIgnoreUsing(columns, query) {
3731
+ const rows = await this.resolveInsertUsingRows(columns, query);
3732
+ if (rows.length === 0) return 0;
3733
+ return this.insertOrIgnore(rows);
3734
+ }
3735
+ /**
3638
3736
  * Updates records matching the current query constraints with the
3639
3737
  * specified data and returns the updated record(s) as model instance(s).
3640
3738
  *
@@ -3652,6 +3750,71 @@ var QueryBuilder = class QueryBuilder {
3652
3750
  return this.model.hydrate(updated);
3653
3751
  }
3654
3752
  /**
3753
+ * Update records using update-many semantics when available.
3754
+ *
3755
+ * @param data
3756
+ * @returns
3757
+ */
3758
+ async updateFrom(data) {
3759
+ const where = this.buildWhere();
3760
+ if (!where) throw new ArkormException("Update requires a where clause.");
3761
+ const delegate = this.delegate;
3762
+ if (typeof delegate.updateMany === "function") {
3763
+ const result = await delegate.updateMany({
3764
+ where,
3765
+ data
3766
+ });
3767
+ return this.resolveAffectedCount(result, 0);
3768
+ }
3769
+ await this.update(data);
3770
+ return 1;
3771
+ }
3772
+ /**
3773
+ * Insert a record when no match exists, otherwise update the matching record.
3774
+ *
3775
+ * @param attributes
3776
+ * @param values
3777
+ * @returns
3778
+ */
3779
+ async updateOrInsert(attributes, values = {}) {
3780
+ const exists = await this.delegate.findFirst({ where: attributes }) != null;
3781
+ const resolvedValues = typeof values === "function" ? await values(exists) : values;
3782
+ if (!exists) {
3783
+ await this.delegate.create({ data: {
3784
+ ...attributes,
3785
+ ...resolvedValues
3786
+ } });
3787
+ return true;
3788
+ }
3789
+ return await this.clone().where(attributes).update(resolvedValues) != null;
3790
+ }
3791
+ /**
3792
+ * Insert new records or update existing records by one or more unique keys.
3793
+ *
3794
+ * @param values
3795
+ * @param uniqueBy
3796
+ * @param update
3797
+ * @returns
3798
+ */
3799
+ async upsert(values, uniqueBy, update = null) {
3800
+ if (values.length === 0) return 0;
3801
+ const uniqueKeys = Array.isArray(uniqueBy) ? uniqueBy : [uniqueBy];
3802
+ let affected = 0;
3803
+ for (const row of values) {
3804
+ const attributes = uniqueKeys.reduce((all, key) => {
3805
+ all[key] = row[key];
3806
+ return all;
3807
+ }, {});
3808
+ const updatePayload = (update ?? Object.keys(row).filter((key) => !uniqueKeys.includes(key))).reduce((all, key) => {
3809
+ if (key in row) all[key] = row[key];
3810
+ return all;
3811
+ }, {});
3812
+ await this.updateOrInsert(attributes, updatePayload);
3813
+ affected += 1;
3814
+ }
3815
+ return affected;
3816
+ }
3817
+ /**
3655
3818
  * Deletes records matching the current query constraints and returns
3656
3819
  * the deleted record(s) as model instance(s).
3657
3820
  *
@@ -3690,6 +3853,43 @@ var QueryBuilder = class QueryBuilder {
3690
3853
  async doesntExist() {
3691
3854
  return !await this.exists();
3692
3855
  }
3856
+ normalizeInsertPayloads(values) {
3857
+ if (Array.isArray(values)) return values;
3858
+ return [values];
3859
+ }
3860
+ resolveAffectedCount(result, fallback) {
3861
+ if (typeof result === "number") return result;
3862
+ if (result && typeof result === "object" && "count" in result) {
3863
+ const candidate = result.count;
3864
+ if (typeof candidate === "number") return candidate;
3865
+ }
3866
+ return fallback;
3867
+ }
3868
+ async resolveInsertUsingRows(columns, query) {
3869
+ const resolvedQuery = typeof query === "function" ? await query() : query;
3870
+ return (await this.resolveInsertUsingSource(resolvedQuery)).map((row) => {
3871
+ return columns.reduce((record, column) => {
3872
+ record[column] = row[column];
3873
+ return record;
3874
+ }, {});
3875
+ });
3876
+ }
3877
+ async resolveInsertUsingSource(source) {
3878
+ if (source && typeof source === "object") {
3879
+ const asBuilder = source;
3880
+ if (typeof asBuilder.get === "function") {
3881
+ const collection = await asBuilder.get();
3882
+ if (typeof collection.all === "function") return collection.all().map((item) => {
3883
+ const asModel = item;
3884
+ if (typeof asModel.getRawAttributes === "function") return asModel.getRawAttributes();
3885
+ return item;
3886
+ });
3887
+ }
3888
+ if (Array.isArray(source)) return source;
3889
+ }
3890
+ if (Array.isArray(source)) return source;
3891
+ throw new ArkormException("insertUsing expects a query builder, array of records, or async resolver.");
3892
+ }
3693
3893
  /**
3694
3894
  * Execute callback when no records exist.
3695
3895
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkormx",
3
- "version": "0.1.11",
3
+ "version": "0.2.1",
4
4
  "description": "Modern TypeScript-first ORM for Node.js.",
5
5
  "keywords": [
6
6
  "orm",