@vandenberghinc/volt 1.2.2 → 1.2.4
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/backend/dist/cjs/backend/src/database/collection.d.ts +17 -10
- package/backend/dist/cjs/backend/src/database/collection.js +289 -126
- package/backend/dist/cjs/backend/src/payments/stripe/products.js +21 -6
- package/backend/dist/cjs/backend/src/stream.d.ts +58 -6
- package/backend/dist/cjs/backend/src/stream.js +91 -12
- package/backend/dist/cjs/backend/src/users.d.ts +11 -4
- package/backend/dist/cjs/backend/src/users.js +93 -26
- package/backend/dist/esm/backend/src/database/collection.d.ts +17 -10
- package/backend/dist/esm/backend/src/database/collection.js +311 -152
- package/backend/dist/esm/backend/src/database/filters/strict_update_filter_test.js +10 -0
- package/backend/dist/esm/backend/src/payments/stripe/products.js +24 -3
- package/backend/dist/esm/backend/src/stream.d.ts +58 -6
- package/backend/dist/esm/backend/src/stream.js +96 -12
- package/backend/dist/esm/backend/src/users.d.ts +11 -4
- package/backend/dist/esm/backend/src/users.js +95 -27
- package/frontend/dist/backend/src/database/collection.d.ts +17 -10
- package/frontend/dist/backend/src/database/collection.js +311 -152
- package/frontend/dist/backend/src/payments/stripe/products.js +24 -3
- package/frontend/dist/backend/src/stream.d.ts +58 -6
- package/frontend/dist/backend/src/stream.js +96 -12
- package/frontend/dist/backend/src/users.d.ts +11 -4
- package/frontend/dist/backend/src/users.js +95 -27
- package/frontend/dist/frontend/src/modules/user.js +3 -2
- package/frontend/dist/frontend/src/ui/table.d.ts +2 -2
- package/frontend/dist/frontend/src/ui/table.js +3 -6
- package/package.json +2 -2
|
@@ -159,6 +159,23 @@ export declare class Collection<Data extends mongodb.Document = mongodb.Document
|
|
|
159
159
|
* - Plain object without '$' keys → NOT valid for updateOne/findOneAndUpdate.
|
|
160
160
|
*/
|
|
161
161
|
private _is_operator_update_or_pipeline;
|
|
162
|
+
private _index_key_signature;
|
|
163
|
+
private _keys_equal;
|
|
164
|
+
private _normalize_index_opts;
|
|
165
|
+
/**
|
|
166
|
+
* Drop all indexes that are NOT part of this._init_indexes, excluding _id_ (and TTL index if enabled).
|
|
167
|
+
*
|
|
168
|
+
* @note We match by key pattern rather than name because names can differ.
|
|
169
|
+
*/
|
|
170
|
+
private _drop_non_init_indexes;
|
|
171
|
+
/**
|
|
172
|
+
* Creates indexes on collections.
|
|
173
|
+
*
|
|
174
|
+
* @note When transaction mode is enabled, the session option will not be used.
|
|
175
|
+
*
|
|
176
|
+
* @param opts The index create options.
|
|
177
|
+
*/
|
|
178
|
+
private _create_index;
|
|
162
179
|
/**
|
|
163
180
|
* Initialize the collection, creating indexes and setting up TTL if needed.
|
|
164
181
|
* @returns The initialized collection instance.
|
|
@@ -215,16 +232,6 @@ export declare class Collection<Data extends mongodb.Document = mongodb.Document
|
|
|
215
232
|
* @docs
|
|
216
233
|
*/
|
|
217
234
|
has_index(index: string): Promise<boolean>;
|
|
218
|
-
/**
|
|
219
|
-
* Creates indexes on collections.
|
|
220
|
-
*
|
|
221
|
-
* @note When transaction mode is enabled, the session option will not be used.
|
|
222
|
-
*
|
|
223
|
-
* @param opts The index create options.
|
|
224
|
-
*
|
|
225
|
-
* @docs
|
|
226
|
-
*/
|
|
227
|
-
create_index(opts: string | Collection.IndexOpts): Promise<string>;
|
|
228
235
|
/**
|
|
229
236
|
* Standalone helper: merge `source` into `target` for missing keys only.
|
|
230
237
|
* Clones assigned nested objects/arrays/dates once (when `clone` is true).
|
|
@@ -462,6 +462,293 @@ class Collection {
|
|
|
462
462
|
_is_operator_update_or_pipeline(operation) {
|
|
463
463
|
return Array.isArray(operation) || operation && typeof operation === "object" && Object.keys(operation).some((k) => k[0] === "$");
|
|
464
464
|
}
|
|
465
|
+
_index_key_signature(keys) {
|
|
466
|
+
return Object.entries(keys).map(([k, v]) => `${k}:${v}`).join("|");
|
|
467
|
+
}
|
|
468
|
+
_keys_equal(a, b) {
|
|
469
|
+
const aEnt = Object.entries(a);
|
|
470
|
+
const bEnt = Object.entries(b);
|
|
471
|
+
if (aEnt.length !== bEnt.length)
|
|
472
|
+
return false;
|
|
473
|
+
for (let i = 0; i < aEnt.length; i++) {
|
|
474
|
+
const [ak, av] = aEnt[i];
|
|
475
|
+
const [bk, bv] = bEnt[i];
|
|
476
|
+
if (ak !== bk || av !== bv)
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
_normalize_index_opts(opts) {
|
|
482
|
+
let key;
|
|
483
|
+
let keys;
|
|
484
|
+
let options;
|
|
485
|
+
let unique;
|
|
486
|
+
let sparse;
|
|
487
|
+
let forced = false;
|
|
488
|
+
if (typeof opts === "string") {
|
|
489
|
+
key = opts;
|
|
490
|
+
unique = void 0;
|
|
491
|
+
sparse = void 0;
|
|
492
|
+
} else {
|
|
493
|
+
({ key, keys, forced = false } = opts);
|
|
494
|
+
const user_options = opts.options;
|
|
495
|
+
options = user_options ? { ...user_options } : void 0;
|
|
496
|
+
if (opts.unique != null && options?.unique != null && opts.unique !== options.unique) {
|
|
497
|
+
throw new import_errors.InvalidUsageError({
|
|
498
|
+
message: `Encountered different values for attribute 'unique': ${opts.unique} and 'options.unique': ${options.unique}.`,
|
|
499
|
+
reason: "invalid_unique_option"
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
unique = opts.unique ?? options?.unique;
|
|
503
|
+
if (opts.sparse != null && options?.sparse != null && opts.sparse !== options.sparse) {
|
|
504
|
+
throw new import_errors.InvalidUsageError({
|
|
505
|
+
message: `Encountered different values for attribute 'sparse': ${opts.sparse} and 'options.sparse': ${options.sparse}.`,
|
|
506
|
+
reason: "invalid_sparse_option"
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
sparse = opts.sparse ?? options?.sparse;
|
|
510
|
+
}
|
|
511
|
+
if (unique != null) {
|
|
512
|
+
options = options || {};
|
|
513
|
+
options.unique = unique;
|
|
514
|
+
}
|
|
515
|
+
if (sparse != null) {
|
|
516
|
+
options = options || {};
|
|
517
|
+
options.sparse = sparse;
|
|
518
|
+
}
|
|
519
|
+
let keys_obj;
|
|
520
|
+
if (key) {
|
|
521
|
+
keys_obj = { [key]: 1 };
|
|
522
|
+
} else if (Array.isArray(keys) && keys.length > 0) {
|
|
523
|
+
keys_obj = {};
|
|
524
|
+
for (const k of keys)
|
|
525
|
+
keys_obj[k] = 1;
|
|
526
|
+
} else if (keys != null && typeof keys === "object") {
|
|
527
|
+
keys_obj = keys;
|
|
528
|
+
} else {
|
|
529
|
+
throw new import_errors.InvalidUsageError({
|
|
530
|
+
message: "Define one of the following parameters: [key, keys].",
|
|
531
|
+
reason: "invalid_index_definition"
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
return { keys_obj, options, forced };
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Drop all indexes that are NOT part of this._init_indexes, excluding _id_ (and TTL index if enabled).
|
|
538
|
+
*
|
|
539
|
+
* @note We match by key pattern rather than name because names can differ.
|
|
540
|
+
*/
|
|
541
|
+
async _drop_non_init_indexes() {
|
|
542
|
+
this.assert_not_transaction_based();
|
|
543
|
+
this.assert_init();
|
|
544
|
+
const existing = await this._col.listIndexes().toArray();
|
|
545
|
+
const desired = /* @__PURE__ */ new Set();
|
|
546
|
+
for (const item of this._init_indexes ?? []) {
|
|
547
|
+
const { keys_obj } = this._normalize_index_opts(item);
|
|
548
|
+
desired.add(this._index_key_signature(keys_obj));
|
|
549
|
+
}
|
|
550
|
+
const protected_names = /* @__PURE__ */ new Set(["_id_"]);
|
|
551
|
+
const keep_ttl = this.ttl_enabled;
|
|
552
|
+
const ttl_sig = this._index_key_signature({ __ttl_timestamp: 1 });
|
|
553
|
+
for (const ix of existing) {
|
|
554
|
+
const name = ix?.name;
|
|
555
|
+
if (!name)
|
|
556
|
+
continue;
|
|
557
|
+
if (protected_names.has(name))
|
|
558
|
+
continue;
|
|
559
|
+
const keyObj = ix.key;
|
|
560
|
+
if (!keyObj)
|
|
561
|
+
continue;
|
|
562
|
+
const sig = this._index_key_signature(keyObj);
|
|
563
|
+
if (desired.has(sig))
|
|
564
|
+
continue;
|
|
565
|
+
if (keep_ttl && sig === ttl_sig)
|
|
566
|
+
continue;
|
|
567
|
+
try {
|
|
568
|
+
this.db.server.log(3, `Dropping stale index "${name}" on collection: ${this.name}`);
|
|
569
|
+
await this._col.dropIndex(name);
|
|
570
|
+
} catch (err) {
|
|
571
|
+
if (err?.codeName !== "IndexNotFound")
|
|
572
|
+
throw err;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Creates indexes on collections.
|
|
578
|
+
*
|
|
579
|
+
* @note When transaction mode is enabled, the session option will not be used.
|
|
580
|
+
*
|
|
581
|
+
* @param opts The index create options.
|
|
582
|
+
*/
|
|
583
|
+
async _create_index(opts) {
|
|
584
|
+
this.assert_not_transaction_based();
|
|
585
|
+
if (!this.initialized) {
|
|
586
|
+
await this.init();
|
|
587
|
+
}
|
|
588
|
+
this.assert_init();
|
|
589
|
+
const { keys_obj, options, forced } = this._normalize_index_opts(opts);
|
|
590
|
+
const drop_index = async () => {
|
|
591
|
+
const existing = await this._col.listIndexes().toArray();
|
|
592
|
+
const match = existing.find((ix) => {
|
|
593
|
+
const ix_key = ix?.key;
|
|
594
|
+
if (!ix_key)
|
|
595
|
+
return false;
|
|
596
|
+
return this._keys_equal(ix_key, keys_obj);
|
|
597
|
+
});
|
|
598
|
+
if (match?.name) {
|
|
599
|
+
try {
|
|
600
|
+
await this._col.dropIndex(match.name);
|
|
601
|
+
} catch (err) {
|
|
602
|
+
if (err?.codeName !== "IndexNotFound")
|
|
603
|
+
throw err;
|
|
604
|
+
}
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (options?.name) {
|
|
608
|
+
try {
|
|
609
|
+
await this._col.dropIndex(options.name);
|
|
610
|
+
} catch (err) {
|
|
611
|
+
if (err?.codeName !== "IndexNotFound")
|
|
612
|
+
throw err;
|
|
613
|
+
}
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const synthesized = Object.entries(keys_obj).map(([k, v]) => `${k}_${v}`).join("_");
|
|
617
|
+
try {
|
|
618
|
+
await this._col.dropIndex(synthesized);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
if (err?.codeName !== "IndexNotFound")
|
|
621
|
+
throw err;
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
try {
|
|
625
|
+
try {
|
|
626
|
+
return await this._col.createIndex(keys_obj, options);
|
|
627
|
+
} catch (err) {
|
|
628
|
+
if (forced && err && typeof err === "object" && err.codeName === "IndexKeySpecsConflict") {
|
|
629
|
+
await drop_index();
|
|
630
|
+
return await this._col.createIndex(keys_obj, options);
|
|
631
|
+
}
|
|
632
|
+
throw err;
|
|
633
|
+
}
|
|
634
|
+
} catch (err) {
|
|
635
|
+
throw new Error(`Failed to create index on collection "${this.name}": ${err}`, { cause: err });
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
// async create_index(opts: string | Collection.IndexOpts): Promise<string> {
|
|
639
|
+
// // Not supported on transaction-based collections.
|
|
640
|
+
// this.assert_not_transaction_based();
|
|
641
|
+
// // Ensure initialized
|
|
642
|
+
// if (!this.initialized) { await this.init(); } this.assert_init();
|
|
643
|
+
// // ---- Normalize inputs ----
|
|
644
|
+
// let key: string | undefined;
|
|
645
|
+
// let keys: string[] | Record<string, number> | undefined;
|
|
646
|
+
// let options: mongodb.CreateIndexesOptions | undefined;
|
|
647
|
+
// let unique: boolean | undefined;
|
|
648
|
+
// let sparse: boolean | undefined;
|
|
649
|
+
// let forced = false;
|
|
650
|
+
// if (typeof opts === "string") {
|
|
651
|
+
// key = opts;
|
|
652
|
+
// unique = undefined;
|
|
653
|
+
// sparse = undefined;
|
|
654
|
+
// } else {
|
|
655
|
+
// ({ key, keys, forced = false } = opts);
|
|
656
|
+
// const options = opts.options as unknown as undefined | mongodb.CreateIndexesOptions;
|
|
657
|
+
// // Conflict guard between `unique` and `options.unique`
|
|
658
|
+
// if (opts.unique != null && options?.unique != null && opts.unique !== options.unique) {
|
|
659
|
+
// throw new InvalidUsageError({
|
|
660
|
+
// message: `Encountered different values for attribute 'unique': ${opts.unique} and 'options.unique': ${options.unique}.`,
|
|
661
|
+
// reason: "invalid_unique_option",
|
|
662
|
+
// });
|
|
663
|
+
// }
|
|
664
|
+
// unique = opts.unique ?? options?.unique;
|
|
665
|
+
// // Conflict guard between `sparse` and `options.sparse`
|
|
666
|
+
// if (opts.sparse != null && options?.sparse != null && opts.sparse !== options.sparse) {
|
|
667
|
+
// throw new InvalidUsageError({
|
|
668
|
+
// message: `Encountered different values for attribute 'sparse': ${opts.sparse} and 'options.sparse': ${options.sparse}.`,
|
|
669
|
+
// reason: "invalid_sparse_option",
|
|
670
|
+
// });
|
|
671
|
+
// }
|
|
672
|
+
// sparse = opts.sparse ?? options?.sparse;
|
|
673
|
+
// }
|
|
674
|
+
// // Ensure `unique` in options when provided
|
|
675
|
+
// if (unique) {
|
|
676
|
+
// options = options || {};
|
|
677
|
+
// options.unique = unique;
|
|
678
|
+
// }
|
|
679
|
+
// // Ensure `sparse` in options when provided
|
|
680
|
+
// if (sparse) {
|
|
681
|
+
// options = options || {};
|
|
682
|
+
// options.sparse = sparse;
|
|
683
|
+
// }
|
|
684
|
+
// // Build keys object
|
|
685
|
+
// let keys_obj: Record<string, number>;
|
|
686
|
+
// if (key) {
|
|
687
|
+
// keys_obj = { [key]: 1 };
|
|
688
|
+
// } else if (Array.isArray(keys) && keys.length > 0) {
|
|
689
|
+
// keys_obj = {};
|
|
690
|
+
// for (const k of keys) keys_obj[k] = 1;
|
|
691
|
+
// } else if (keys != null && typeof keys === "object") {
|
|
692
|
+
// keys_obj = keys as Record<string, number>;
|
|
693
|
+
// } else {
|
|
694
|
+
// throw new InvalidUsageError({
|
|
695
|
+
// message: "Define one of the following parameters: [key, keys].",
|
|
696
|
+
// reason: "invalid_index_definition",
|
|
697
|
+
// });
|
|
698
|
+
// }
|
|
699
|
+
// const drop_index = async () => {
|
|
700
|
+
// try {
|
|
701
|
+
// const existing = await this._col.listIndexes().toArray();
|
|
702
|
+
// const match = existing.find(ix => {
|
|
703
|
+
// const ix_key = ix?.key as Record<string, number> | undefined;
|
|
704
|
+
// if (!ix_key) return false;
|
|
705
|
+
// const a = Object.entries(ix_key);
|
|
706
|
+
// const b = Object.entries(keys_obj);
|
|
707
|
+
// if (a.length !== b.length) return false;
|
|
708
|
+
// // exact key-value equality (order-insensitive)
|
|
709
|
+
// const as = new Map(a);
|
|
710
|
+
// for (const [kk, vv] of b) {
|
|
711
|
+
// if (as.get(kk) !== vv) return false;
|
|
712
|
+
// }
|
|
713
|
+
// return true;
|
|
714
|
+
// });
|
|
715
|
+
// // Prefer matched key's real name
|
|
716
|
+
// if (match?.name) {
|
|
717
|
+
// try { await this._col.dropIndex(match.name); }
|
|
718
|
+
// catch (err: any) { if (err?.codeName !== "IndexNotFound") throw err; }
|
|
719
|
+
// } else if (options?.name) {
|
|
720
|
+
// try { await this._col.dropIndex(options.name); }
|
|
721
|
+
// catch (err: any) { if (err?.codeName !== "IndexNotFound") throw err; }
|
|
722
|
+
// } else {
|
|
723
|
+
// // last-resort synthesized name (simple cases)
|
|
724
|
+
// const synthesized = Object.entries(keys_obj).map(([k, v]) => `${k}_${v}`).join("_");
|
|
725
|
+
// try { await this._col.dropIndex(synthesized); }
|
|
726
|
+
// catch (err: any) { if (err?.codeName !== "IndexNotFound") throw err; }
|
|
727
|
+
// }
|
|
728
|
+
// } catch (err) {
|
|
729
|
+
// // If listIndexes itself fails for some reason, do not hide the error
|
|
730
|
+
// throw new Error(`Failed to create index on collection "${this.name}": ${err}`, { cause: err });
|
|
731
|
+
// }
|
|
732
|
+
// }
|
|
733
|
+
// try {
|
|
734
|
+
// // Create (or re-create)
|
|
735
|
+
// try {
|
|
736
|
+
// return await this._col.createIndex(keys_obj, options);
|
|
737
|
+
// }
|
|
738
|
+
// // Retry once on IndexKeySpecsConflict when forced=true
|
|
739
|
+
// catch (err) {
|
|
740
|
+
// if (forced && err && typeof err === "object" && (
|
|
741
|
+
// (err as any).codeName === "IndexKeySpecsConflict"
|
|
742
|
+
// )) {
|
|
743
|
+
// await drop_index();
|
|
744
|
+
// return await this._col.createIndex(keys_obj, options);
|
|
745
|
+
// }
|
|
746
|
+
// throw err;
|
|
747
|
+
// }
|
|
748
|
+
// } catch (err) {
|
|
749
|
+
// throw new Error(`Failed to create index on collection "${this.name}": ${err}`, { cause: err });
|
|
750
|
+
// }
|
|
751
|
+
// }
|
|
465
752
|
// -------------------------------------------------------------------
|
|
466
753
|
// Public methods.
|
|
467
754
|
// -------------------------------------------------------------------
|
|
@@ -524,10 +811,11 @@ class Collection {
|
|
|
524
811
|
this.db.server.log(3, "Setting up TTL index for collection: " + this.name);
|
|
525
812
|
await this._setup_ttl();
|
|
526
813
|
}
|
|
814
|
+
await this._drop_non_init_indexes();
|
|
527
815
|
if (this._init_indexes?.length) {
|
|
528
816
|
for (const item of this._init_indexes) {
|
|
529
817
|
this.db.server.log(3, "Creating index " + JSON.stringify(item) + " on collection: " + this.name);
|
|
530
|
-
await this.
|
|
818
|
+
await this._create_index(item);
|
|
531
819
|
}
|
|
532
820
|
}
|
|
533
821
|
} else {
|
|
@@ -631,131 +919,6 @@ class Collection {
|
|
|
631
919
|
this.assert_not_transaction_based();
|
|
632
920
|
return (await this._col.listIndexes().toArray()).some((x) => x.name === index);
|
|
633
921
|
}
|
|
634
|
-
/**
|
|
635
|
-
* Creates indexes on collections.
|
|
636
|
-
*
|
|
637
|
-
* @note When transaction mode is enabled, the session option will not be used.
|
|
638
|
-
*
|
|
639
|
-
* @param opts The index create options.
|
|
640
|
-
*
|
|
641
|
-
* @docs
|
|
642
|
-
*/
|
|
643
|
-
async create_index(opts) {
|
|
644
|
-
this.assert_not_transaction_based();
|
|
645
|
-
if (!this.initialized) {
|
|
646
|
-
await this.init();
|
|
647
|
-
}
|
|
648
|
-
this.assert_init();
|
|
649
|
-
let key;
|
|
650
|
-
let keys;
|
|
651
|
-
let options;
|
|
652
|
-
let unique;
|
|
653
|
-
let sparse;
|
|
654
|
-
let forced = false;
|
|
655
|
-
if (typeof opts === "string") {
|
|
656
|
-
key = opts;
|
|
657
|
-
unique = void 0;
|
|
658
|
-
sparse = void 0;
|
|
659
|
-
} else {
|
|
660
|
-
({ key, keys, forced = false } = opts);
|
|
661
|
-
const options2 = opts.options;
|
|
662
|
-
if (opts.unique != null && options2?.unique != null && opts.unique !== options2.unique) {
|
|
663
|
-
throw new import_errors.InvalidUsageError({
|
|
664
|
-
message: `Encountered different values for attribute 'unique': ${opts.unique} and 'options.unique': ${options2.unique}.`,
|
|
665
|
-
reason: "invalid_unique_option"
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
unique = opts.unique ?? options2?.unique;
|
|
669
|
-
if (opts.sparse != null && options2?.sparse != null && opts.sparse !== options2.sparse) {
|
|
670
|
-
throw new import_errors.InvalidUsageError({
|
|
671
|
-
message: `Encountered different values for attribute 'sparse': ${opts.sparse} and 'options.sparse': ${options2.sparse}.`,
|
|
672
|
-
reason: "invalid_sparse_option"
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
sparse = opts.sparse ?? options2?.sparse;
|
|
676
|
-
}
|
|
677
|
-
if (unique) {
|
|
678
|
-
options = options || {};
|
|
679
|
-
options.unique = unique;
|
|
680
|
-
}
|
|
681
|
-
if (sparse) {
|
|
682
|
-
options = options || {};
|
|
683
|
-
options.sparse = sparse;
|
|
684
|
-
}
|
|
685
|
-
let keys_obj;
|
|
686
|
-
if (key) {
|
|
687
|
-
keys_obj = { [key]: 1 };
|
|
688
|
-
} else if (Array.isArray(keys) && keys.length > 0) {
|
|
689
|
-
keys_obj = {};
|
|
690
|
-
for (const k of keys)
|
|
691
|
-
keys_obj[k] = 1;
|
|
692
|
-
} else if (keys != null && typeof keys === "object") {
|
|
693
|
-
keys_obj = keys;
|
|
694
|
-
} else {
|
|
695
|
-
throw new import_errors.InvalidUsageError({
|
|
696
|
-
message: "Define one of the following parameters: [key, keys].",
|
|
697
|
-
reason: "invalid_index_definition"
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
const drop_index = async () => {
|
|
701
|
-
try {
|
|
702
|
-
const existing = await this._col.listIndexes().toArray();
|
|
703
|
-
const match = existing.find((ix) => {
|
|
704
|
-
const ix_key = ix?.key;
|
|
705
|
-
if (!ix_key)
|
|
706
|
-
return false;
|
|
707
|
-
const a = Object.entries(ix_key);
|
|
708
|
-
const b = Object.entries(keys_obj);
|
|
709
|
-
if (a.length !== b.length)
|
|
710
|
-
return false;
|
|
711
|
-
const as = new Map(a);
|
|
712
|
-
for (const [kk, vv] of b) {
|
|
713
|
-
if (as.get(kk) !== vv)
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
|
-
return true;
|
|
717
|
-
});
|
|
718
|
-
if (match?.name) {
|
|
719
|
-
try {
|
|
720
|
-
await this._col.dropIndex(match.name);
|
|
721
|
-
} catch (err) {
|
|
722
|
-
if (err?.codeName !== "IndexNotFound")
|
|
723
|
-
throw err;
|
|
724
|
-
}
|
|
725
|
-
} else if (options?.name) {
|
|
726
|
-
try {
|
|
727
|
-
await this._col.dropIndex(options.name);
|
|
728
|
-
} catch (err) {
|
|
729
|
-
if (err?.codeName !== "IndexNotFound")
|
|
730
|
-
throw err;
|
|
731
|
-
}
|
|
732
|
-
} else {
|
|
733
|
-
const synthesized = Object.entries(keys_obj).map(([k, v]) => `${k}_${v}`).join("_");
|
|
734
|
-
try {
|
|
735
|
-
await this._col.dropIndex(synthesized);
|
|
736
|
-
} catch (err) {
|
|
737
|
-
if (err?.codeName !== "IndexNotFound")
|
|
738
|
-
throw err;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
} catch (err) {
|
|
742
|
-
throw new Error(`Failed to create index on collection "${this.name}": ${err}`, { cause: err });
|
|
743
|
-
}
|
|
744
|
-
};
|
|
745
|
-
try {
|
|
746
|
-
try {
|
|
747
|
-
return await this._col.createIndex(keys_obj, options);
|
|
748
|
-
} catch (err) {
|
|
749
|
-
if (forced && err && typeof err === "object" && err.codeName === "IndexKeySpecsConflict") {
|
|
750
|
-
await drop_index();
|
|
751
|
-
return await this._col.createIndex(keys_obj, options);
|
|
752
|
-
}
|
|
753
|
-
throw err;
|
|
754
|
-
}
|
|
755
|
-
} catch (err) {
|
|
756
|
-
throw new Error(`Failed to create index on collection "${this.name}": ${err}`, { cause: err });
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
922
|
/**
|
|
760
923
|
* Standalone helper: merge `source` into `target` for missing keys only.
|
|
761
924
|
* Clones assigned nested objects/arrays/dates once (when `clone` is true).
|
|
@@ -123,7 +123,8 @@ async function list_all_stripe_products(client) {
|
|
|
123
123
|
for (; ; ) {
|
|
124
124
|
const page = await (0, import_utils.stripe_api_call)(() => client.products.list({
|
|
125
125
|
limit: stripe_list_page_size,
|
|
126
|
-
starting_after
|
|
126
|
+
starting_after,
|
|
127
|
+
expand: ["data.default_price"]
|
|
127
128
|
}), { operation: "products.list_all", starting_after });
|
|
128
129
|
all_products.push(...page.data);
|
|
129
130
|
if (!page.has_more || page.data.length === 0) {
|
|
@@ -239,7 +240,8 @@ async function create_stripe_product(client, server, product) {
|
|
|
239
240
|
images: product.images,
|
|
240
241
|
metadata: {
|
|
241
242
|
[app_product_id_metadata_key]: product.id
|
|
242
|
-
}
|
|
243
|
+
},
|
|
244
|
+
expand: ["default_price"]
|
|
243
245
|
}, { idempotencyKey: (0, import_utils.generate_random_idempotency_key)(`create_product_${product.id}`) }), { operation: "products.create", app_product_id: product.id });
|
|
244
246
|
}
|
|
245
247
|
async function update_stripe_product_if_needed(client, server, stripe_product, product) {
|
|
@@ -255,11 +257,24 @@ async function update_stripe_product_if_needed(client, server, stripe_product, p
|
|
|
255
257
|
name: product.name,
|
|
256
258
|
description: product.description,
|
|
257
259
|
tax_code: product.tax_code,
|
|
258
|
-
images: product.images
|
|
260
|
+
images: product.images,
|
|
261
|
+
expand: ["default_price"]
|
|
259
262
|
}, { idempotencyKey: (0, import_utils.generate_random_idempotency_key)(`update_product_${product.id}_${stripe_product.id}`) }), { operation: "products.update", app_product_id: product.id, stripe_product_id: stripe_product.id });
|
|
260
263
|
}
|
|
261
|
-
async function update_stripe_product_default_price_if_needed(client, server, stripe_product, default_price) {
|
|
262
|
-
|
|
264
|
+
async function update_stripe_product_default_price_if_needed(client, server, stripe_product, default_price, other_plans_from_parent_subscription) {
|
|
265
|
+
let default_price_id = null;
|
|
266
|
+
if (typeof stripe_product.default_price === "string") {
|
|
267
|
+
default_price_id = stripe_product.default_price;
|
|
268
|
+
} else if (stripe_product.default_price && typeof stripe_product.default_price === "object") {
|
|
269
|
+
default_price_id = stripe_product.default_price.id;
|
|
270
|
+
}
|
|
271
|
+
if (!default_price_id) {
|
|
272
|
+
const fetched = await (0, import_utils.stripe_api_call)(() => client.products.retrieve(stripe_product.id, {
|
|
273
|
+
expand: ["default_price"]
|
|
274
|
+
}), { operation: "products.retrieve_for_default_price", stripe_product_id: stripe_product.id });
|
|
275
|
+
default_price_id = typeof fetched.default_price === "string" ? fetched.default_price : fetched.default_price?.id ?? null;
|
|
276
|
+
}
|
|
277
|
+
if (default_price_id === default_price.id || other_plans_from_parent_subscription?.some((plan) => plan.stripe_price_id === default_price_id)) {
|
|
263
278
|
return;
|
|
264
279
|
}
|
|
265
280
|
server.log(0, `Updating default price for Stripe product '${stripe_product.id}' to price '${default_price.id}'`);
|
|
@@ -526,7 +541,7 @@ async function initialize_product(client, server, product, stripe_products_by_ap
|
|
|
526
541
|
const updated_prices = [...active_prices, stripe_price];
|
|
527
542
|
active_prices_by_stripe_product_id.set(stripe_product.id, updated_prices);
|
|
528
543
|
}
|
|
529
|
-
await update_stripe_product_default_price_if_needed(client, server, stripe_product, stripe_price);
|
|
544
|
+
await update_stripe_product_default_price_if_needed(client, server, stripe_product, stripe_price, initialized_plans);
|
|
530
545
|
initialized_plans.push({
|
|
531
546
|
...plan,
|
|
532
547
|
type: "subscription_plan",
|
|
@@ -362,17 +362,69 @@ export declare class Stream {
|
|
|
362
362
|
*/
|
|
363
363
|
remove_headers(...names: string[]): this;
|
|
364
364
|
/**
|
|
365
|
-
* Set a cookie
|
|
365
|
+
* Set a cookie to be sent with the response.
|
|
366
|
+
*
|
|
367
|
+
* Accepts either:
|
|
368
|
+
* 1) a pre-built cookie header string (used as-is, no validation), or
|
|
369
|
+
* 2) a structured object describing the cookie, from which a standards-compliant
|
|
370
|
+
* cookie string will be generated.
|
|
371
|
+
*
|
|
372
|
+
* If a cookie with the same name already exists in the pending response list,
|
|
373
|
+
* it will be replaced.
|
|
374
|
+
*
|
|
375
|
+
* @warning Cookies are only included in the response when using `send()`,
|
|
376
|
+
* `success()` or `error()`.
|
|
366
377
|
*
|
|
367
|
-
* @warning Will only be added to the response when the user uses `send()`, `success()` or `error()`.
|
|
368
|
-
* @param cookie The cookie string.
|
|
369
378
|
* @example
|
|
370
379
|
* ```ts
|
|
371
|
-
* stream.set_cookie("
|
|
380
|
+
* stream.set_cookie("sid=abc123; Path=/; SameSite=Lax; Secure; HttpOnly");
|
|
381
|
+
*
|
|
382
|
+
* stream.set_cookie({
|
|
383
|
+
* name: "sid",
|
|
384
|
+
* value: session_id,
|
|
385
|
+
* http_only: true,
|
|
386
|
+
* secure: true,
|
|
387
|
+
* same_site: "Lax",
|
|
388
|
+
* path: "/",
|
|
389
|
+
* max_age: 60 * 60 * 24 * 14,
|
|
390
|
+
* });
|
|
372
391
|
* ```
|
|
373
|
-
* @docs
|
|
374
392
|
*/
|
|
375
|
-
set_cookie(cookie: string
|
|
393
|
+
set_cookie(cookie: string | {
|
|
394
|
+
/** Cookie name (required). */
|
|
395
|
+
name: string;
|
|
396
|
+
/** Cookie value. Will be URI-encoded. Defaults to empty string. */
|
|
397
|
+
value?: string | number | boolean | null;
|
|
398
|
+
/** Cookie path attribute. Defaults to "/". */
|
|
399
|
+
path?: string;
|
|
400
|
+
/** Cookie domain attribute. */
|
|
401
|
+
domain?: string;
|
|
402
|
+
/** Max-Age in seconds. Must be a finite number. */
|
|
403
|
+
max_age?: number;
|
|
404
|
+
/** Expiration date (Date or preformatted HTTP date string). */
|
|
405
|
+
expires?: Date | string;
|
|
406
|
+
/** Adds the Secure attribute (HTTPS only). */
|
|
407
|
+
secure?: boolean;
|
|
408
|
+
/** Adds the HttpOnly attribute (not accessible to JS). */
|
|
409
|
+
http_only?: boolean;
|
|
410
|
+
/**
|
|
411
|
+
* SameSite attribute.
|
|
412
|
+
* Use "Lax" for most session cookies.
|
|
413
|
+
* Use "None" only together with `secure: true`.
|
|
414
|
+
*/
|
|
415
|
+
same_site?: "Strict" | "Lax" | "None";
|
|
416
|
+
/**
|
|
417
|
+
* Cookie name prefix.
|
|
418
|
+
* "__Host-" requires: secure=true, path="/", and no domain.
|
|
419
|
+
* "__Secure-" requires: secure=true.
|
|
420
|
+
*/
|
|
421
|
+
prefix?: "__Host-" | "__Secure-";
|
|
422
|
+
/**
|
|
423
|
+
* Additional raw attributes appended verbatim.
|
|
424
|
+
* Example: ["Priority=High"]
|
|
425
|
+
*/
|
|
426
|
+
extra?: string[];
|
|
427
|
+
}): this;
|
|
376
428
|
/**
|
|
377
429
|
* Set cookies that will be sent with the response.
|
|
378
430
|
*
|