@lossless.org/client 1.6.0 → 1.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.
Files changed (51) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/nosqldb/classes.atomicdelete.d.ts +1 -0
  3. package/dist_ts/nosqldb/classes.atomicdelete.js +10 -7
  4. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.d.ts +2 -0
  5. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +9 -5
  6. package/dist_ts/nosqldb/classes.atomicupdate.d.ts +2 -0
  7. package/dist_ts/nosqldb/classes.atomicupdate.js +13 -8
  8. package/dist_ts/nosqldb/classes.collection.d.ts +13 -4
  9. package/dist_ts/nosqldb/classes.collection.js +57 -64
  10. package/dist_ts/nosqldb/classes.db.d.ts +1 -1
  11. package/dist_ts/nosqldb/classes.doc.d.ts +60 -3
  12. package/dist_ts/nosqldb/classes.doc.js +68 -26
  13. package/dist_ts/nosqldb/classes.exactpersistence.d.ts +36 -7
  14. package/dist_ts/nosqldb/classes.exactpersistence.js +92 -81
  15. package/dist_ts/nosqldb/classes.operationdeadline.d.ts +61 -0
  16. package/dist_ts/nosqldb/classes.operationdeadline.js +149 -0
  17. package/dist_ts/nosqldb/classes.persistence.d.ts +16 -1
  18. package/dist_ts/nosqldb/classes.persistence.js +20 -1
  19. package/dist_ts/nosqldb/classes.session.d.ts +73 -2
  20. package/dist_ts/nosqldb/classes.session.js +164 -38
  21. package/dist_ts/objectstorage/classes.bucket.d.ts +61 -3
  22. package/dist_ts/objectstorage/classes.bucket.js +243 -184
  23. package/dist_ts/objectstorage/classes.directory.js +17 -28
  24. package/dist_ts/objectstorage/classes.smartbucket.d.ts +13 -6
  25. package/dist_ts/objectstorage/classes.smartbucket.js +18 -12
  26. package/dist_ts/objectstorage/classes.watcher.js +1 -2
  27. package/dist_ts/objectstorage/interfaces.d.ts +8 -0
  28. package/dist_ts/objectstorage/internal.bucketrequests.d.ts +14 -0
  29. package/dist_ts/objectstorage/internal.bucketrequests.js +53 -0
  30. package/dist_ts/objectstorage/internal.exactupload.capability.d.ts +12 -0
  31. package/dist_ts/objectstorage/internal.exactupload.capability.js +46 -17
  32. package/package.json +3 -3
  33. package/readme.md +13 -2
  34. package/ts/00_commitinfo_data.ts +1 -1
  35. package/ts/nosqldb/classes.atomicdelete.ts +10 -6
  36. package/ts/nosqldb/classes.atomicfindoneandupdate.ts +10 -4
  37. package/ts/nosqldb/classes.atomicupdate.ts +14 -7
  38. package/ts/nosqldb/classes.collection.ts +82 -73
  39. package/ts/nosqldb/classes.db.ts +1 -1
  40. package/ts/nosqldb/classes.doc.ts +162 -33
  41. package/ts/nosqldb/classes.exactpersistence.ts +131 -93
  42. package/ts/nosqldb/classes.operationdeadline.ts +179 -0
  43. package/ts/nosqldb/classes.persistence.ts +36 -1
  44. package/ts/nosqldb/classes.session.ts +221 -39
  45. package/ts/objectstorage/classes.bucket.ts +350 -218
  46. package/ts/objectstorage/classes.directory.ts +18 -27
  47. package/ts/objectstorage/classes.smartbucket.ts +21 -11
  48. package/ts/objectstorage/classes.watcher.ts +0 -1
  49. package/ts/objectstorage/interfaces.ts +9 -0
  50. package/ts/objectstorage/internal.bucketrequests.ts +70 -0
  51. package/ts/objectstorage/internal.exactupload.capability.ts +105 -24
@@ -25,6 +25,7 @@ import {
25
25
  import { SmartdataDbWatcher } from './classes.watcher.js';
26
26
  import { SmartdataLuceneAdapter } from './classes.lucene.adapter.js';
27
27
  import { executeAtomicDelete, executeAtomicDeleteMany } from './classes.atomicdelete.js';
28
+ import { requireOperationSignal } from './classes.operationdeadline.js';
28
29
  import {
29
30
  executeAtomicFindOneAndReplace, executeAtomicFindOneAndUpdate,
30
31
  } from './classes.atomicfindoneandupdate.js';
@@ -664,7 +665,14 @@ export interface ISmartdataOffsetPageOptions<T> {
664
665
  projection?: TSmartdataProjection<T>;
665
666
  hint?: string;
666
667
  maxTimeMS?: number;
667
- /** Defaults to 5000ms; maximum 120000ms. Covers the cursor's entire lifetime. */
668
+ /**
669
+ * Defaults to 5000ms; maximum 120000ms. Covers the cursor up to its last
670
+ * reply. A page larger than the first batch that stalls in its `getMore`
671
+ * settles within about twice the deadline: the driver's `killCursors` runs
672
+ * on a fresh deadline of the same length before the error surfaces.
673
+ * Inside a transaction with its own deadline that deadline replaces the
674
+ * default, and an explicit value is refused.
675
+ */
668
676
  timeoutMS?: number;
669
677
  signal?: AbortSignal;
670
678
  session?: TSmartdataOrdinarySession;
@@ -673,6 +681,8 @@ export interface ISmartdataOffsetPageOptions<T> {
673
681
  export interface ISmartdataFindOneOptions<T> {
674
682
  projection?: TSmartdataProjection<T>;
675
683
  maxTimeMS?: number;
684
+ /** Client-side deadline for the call's database work (1..120000 ms), including model initialization, server selection and pool waits. */
685
+ timeoutMS?: number;
676
686
  session?: TSmartdataOrdinarySession;
677
687
  }
678
688
 
@@ -765,7 +775,12 @@ export interface ISmartdataGroupedTotalsOptions<T> {
765
775
  */
766
776
  limit: number;
767
777
  maxTimeMS?: number;
768
- /** Client-side deadline for the complete aggregation cursor lifetime. */
778
+ /**
779
+ * Client-side deadline for the aggregation cursor up to its last reply. More
780
+ * groups than the first batch holds need a `getMore`; when that times out the
781
+ * driver's `killCursors` runs on a fresh deadline of the same length, so a
782
+ * stalled database settles the call within about twice the deadline.
783
+ */
769
784
  timeoutMS?: number;
770
785
  signal?: AbortSignal;
771
786
  session?: TSmartdataOrdinarySession;
@@ -943,6 +958,8 @@ export type TSmartdataAtomicFilter<T> =
943
958
  export interface ISmartdataAtomicUpdateOptions {
944
959
  upsert?: boolean;
945
960
  session?: TSmartdataOrdinarySession;
961
+ /** Client-side deadline for the call's database work (1..120000 ms), including model initialization, server selection and pool waits. */
962
+ timeoutMS?: number;
946
963
  }
947
964
 
948
965
  export interface ISmartdataAtomicUpdateResult {
@@ -969,6 +986,8 @@ export interface ISmartdataAtomicFindOneAndUpdateOptions<
969
986
  */
970
987
  sort?: TSmartdataSort<TModel>;
971
988
  session?: TSmartdataOrdinarySession;
989
+ /** Client-side deadline for the call's database work (1..120000 ms), including model initialization, server selection and pool waits. */
990
+ timeoutMS?: number;
972
991
  }
973
992
 
974
993
  export type TSmartdataAtomicFindOneAndUpdateResult<
@@ -989,6 +1008,8 @@ export interface ISmartdataAtomicFindOneAndReplaceOptions {
989
1008
  */
990
1009
  returnDocument?: TSmartdataAtomicReturnDocument;
991
1010
  session?: TSmartdataOrdinarySession;
1011
+ /** Client-side deadline for the call's database work (1..120000 ms), including model initialization, server selection and pool waits. */
1012
+ timeoutMS?: number;
992
1013
  }
993
1014
 
994
1015
  export interface ISmartdataAtomicDeleteOptions {
@@ -997,6 +1018,14 @@ export interface ISmartdataAtomicDeleteOptions {
997
1018
  timeoutMS?: number;
998
1019
  }
999
1020
 
1021
+ export interface ISmartdataAtomicDeleteManyOptions extends ISmartdataAtomicDeleteOptions {
1022
+ /**
1023
+ * Cancels the call. An abort before the delete is sent deletes nothing; an
1024
+ * abort in flight closes the connection and leaves the outcome unknown.
1025
+ */
1026
+ signal?: AbortSignal;
1027
+ }
1028
+
1000
1029
  export interface ISmartdataInsertManyIfAbsentOptions<T> {
1001
1030
  /** A declared, non-serialized @unI identity field. */
1002
1031
  identityField: TSmartdataDocumentPath<T>;
@@ -1017,6 +1046,8 @@ export interface ISmartdataAtomicDeleteResult {
1017
1046
 
1018
1047
  export interface ISmartdataAtomicUpdateManyOptions {
1019
1048
  session?: TSmartdataOrdinarySession;
1049
+ /** Client-side deadline for the call's database work (1..120000 ms), including model initialization, server selection and pool waits. */
1050
+ timeoutMS?: number;
1020
1051
  }
1021
1052
 
1022
1053
  export interface ISmartdataAtomicUpdateManyResult {
@@ -1062,6 +1093,8 @@ export interface ISmartdataSaveIfOptions {
1062
1093
  session?: TSmartdataOrdinarySession;
1063
1094
  /** Defaults to `'ignore'`. */
1064
1095
  absentDeclaredFields?: TSmartdataAbsentDeclaredFields;
1096
+ /** Client-side deadline for the write (1..120000 ms); `beforeSave()` and `afterSave()` run outside it. */
1097
+ timeoutMS?: number;
1065
1098
  }
1066
1099
 
1067
1100
  export interface ISmartdataSaveIfResult {
@@ -1076,6 +1109,7 @@ const normalizeSaveIfOptions = (
1076
1109
  ): {
1077
1110
  session?: TSmartdataOrdinarySession;
1078
1111
  absentDeclaredFields: TSmartdataAbsentDeclaredFields;
1112
+ timeoutMS?: number;
1079
1113
  } => {
1080
1114
  if (optionsArg === undefined) {
1081
1115
  return { absentDeclaredFields: 'ignore' };
@@ -1091,7 +1125,7 @@ const normalizeSaveIfOptions = (
1091
1125
  'Fenced save options must be an inert plain object.',
1092
1126
  );
1093
1127
  }
1094
- const allowedKeys = new Set(['session', 'absentDeclaredFields']);
1128
+ const allowedKeys = new Set(['session', 'absentDeclaredFields', 'timeoutMS']);
1095
1129
  for (const key of Reflect.ownKeys(optionsArg)) {
1096
1130
  if (typeof key !== 'string' || !allowedKeys.has(key)) {
1097
1131
  throw new SmartdataPersistenceError(
@@ -1124,9 +1158,15 @@ const normalizeSaveIfOptions = (
1124
1158
  const session = Object.getOwnPropertyDescriptor(optionsArg, 'session')?.value as
1125
1159
  | TSmartdataOrdinarySession
1126
1160
  | undefined;
1161
+ const timeoutMS = requireBoundedInteger(
1162
+ Object.getOwnPropertyDescriptor(optionsArg, 'timeoutMS')?.value,
1163
+ 'Fenced save timeoutMS',
1164
+ 120_000,
1165
+ );
1127
1166
  return {
1128
1167
  ...(session !== undefined ? { session } : {}),
1129
1168
  absentDeclaredFields: absentDeclaredFields ?? 'ignore',
1169
+ ...(timeoutMS !== undefined ? { timeoutMS } : {}),
1130
1170
  };
1131
1171
  };
1132
1172
 
@@ -1139,6 +1179,7 @@ const normalizeAtomicFindOneAndUpdateOptions = <
1139
1179
  upsert: boolean;
1140
1180
  sort?: unknown;
1141
1181
  session?: TSmartdataOrdinarySession;
1182
+ timeoutMS?: number;
1142
1183
  } => {
1143
1184
  if (
1144
1185
  typeof optionsArg !== 'object'
@@ -1151,7 +1192,7 @@ const normalizeAtomicFindOneAndUpdateOptions = <
1151
1192
  'Atomic find-one-and-update options must be an inert plain object.',
1152
1193
  );
1153
1194
  }
1154
- const allowedKeys = new Set(['returnDocument', 'upsert', 'sort', 'session']);
1195
+ const allowedKeys = new Set(['returnDocument', 'upsert', 'sort', 'session', 'timeoutMS']);
1155
1196
  const keys = Reflect.ownKeys(optionsArg);
1156
1197
  for (const key of keys) {
1157
1198
  if (typeof key !== 'string' || !allowedKeys.has(key)) {
@@ -1195,6 +1236,11 @@ const normalizeAtomicFindOneAndUpdateOptions = <
1195
1236
  }
1196
1237
  const sortDescriptor = Object.getOwnPropertyDescriptor(optionsArg, 'sort');
1197
1238
  const sessionDescriptor = Object.getOwnPropertyDescriptor(optionsArg, 'session');
1239
+ const timeoutMS = requireBoundedInteger(
1240
+ Object.getOwnPropertyDescriptor(optionsArg, 'timeoutMS')?.value,
1241
+ 'Atomic find-one-and-update timeoutMS',
1242
+ 120_000,
1243
+ );
1198
1244
  return {
1199
1245
  returnDocument: returnDocumentDescriptor.value as TReturnDocument,
1200
1246
  upsert: upsertDescriptor?.value === true,
@@ -1204,6 +1250,7 @@ const normalizeAtomicFindOneAndUpdateOptions = <
1204
1250
  ...(sessionDescriptor?.value !== undefined
1205
1251
  ? { session: sessionDescriptor.value as TSmartdataOrdinarySession }
1206
1252
  : {}),
1253
+ ...(timeoutMS !== undefined ? { timeoutMS } : {}),
1207
1254
  };
1208
1255
  };
1209
1256
 
@@ -1212,6 +1259,7 @@ const normalizeAtomicFindOneAndReplaceOptions = (
1212
1259
  ): {
1213
1260
  returnDocument: TSmartdataAtomicReturnDocument;
1214
1261
  session?: TSmartdataOrdinarySession;
1262
+ timeoutMS?: number;
1215
1263
  } => {
1216
1264
  if (optionsArg === undefined) {
1217
1265
  return { returnDocument: 'after' };
@@ -1227,7 +1275,7 @@ const normalizeAtomicFindOneAndReplaceOptions = (
1227
1275
  'Atomic find-one-and-replace options must be an inert plain object.',
1228
1276
  );
1229
1277
  }
1230
- const allowedKeys = new Set(['returnDocument', 'session']);
1278
+ const allowedKeys = new Set(['returnDocument', 'session', 'timeoutMS']);
1231
1279
  for (const key of Reflect.ownKeys(optionsArg)) {
1232
1280
  if (typeof key !== 'string' || !allowedKeys.has(key)) {
1233
1281
  throw new SmartdataPersistenceError(
@@ -1258,9 +1306,15 @@ const normalizeAtomicFindOneAndReplaceOptions = (
1258
1306
  const session = Object.getOwnPropertyDescriptor(optionsArg, 'session')?.value as
1259
1307
  | TSmartdataOrdinarySession
1260
1308
  | undefined;
1309
+ const timeoutMS = requireBoundedInteger(
1310
+ Object.getOwnPropertyDescriptor(optionsArg, 'timeoutMS')?.value,
1311
+ 'Atomic find-one-and-replace timeoutMS',
1312
+ 120_000,
1313
+ );
1261
1314
  return {
1262
1315
  returnDocument: returnDocument ?? 'after',
1263
1316
  ...(session !== undefined ? { session } : {}),
1317
+ ...(timeoutMS !== undefined ? { timeoutMS } : {}),
1264
1318
  };
1265
1319
  };
1266
1320
 
@@ -1281,7 +1335,7 @@ const normalizeAtomicExpressionUpdateOptions = (
1281
1335
  'Atomic update options used with $expr must be an inert ordinary object.',
1282
1336
  );
1283
1337
  }
1284
- const allowedKeys = new Set(['upsert', 'session']);
1338
+ const allowedKeys = new Set(['upsert', 'session', 'timeoutMS']);
1285
1339
  const normalized: ISmartdataAtomicUpdateOptions = {};
1286
1340
  for (const key of Reflect.ownKeys(optionsArg)) {
1287
1341
  if (typeof key !== 'string' || !allowedKeys.has(key)) {
@@ -1314,6 +1368,10 @@ const normalizeAtomicExpressionUpdateOptions = (
1314
1368
  if (descriptor.value !== undefined) {
1315
1369
  normalized.upsert = descriptor.value;
1316
1370
  }
1371
+ } else if (key === 'timeoutMS') {
1372
+ if (descriptor.value !== undefined) {
1373
+ normalized.timeoutMS = descriptor.value as number;
1374
+ }
1317
1375
  } else if (descriptor.value !== undefined) {
1318
1376
  normalized.session = descriptor.value as TSmartdataOrdinarySession;
1319
1377
  }
@@ -3947,7 +4005,11 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
3947
4005
  public static async insert<T extends SmartDataDbDoc<any, any>>(
3948
4006
  this: plugins.tsclass.typeFest.Class<T>,
3949
4007
  documentArg: T,
3950
- opts?: { session?: TSmartdataOrdinarySession },
4008
+ opts?: {
4009
+ session?: TSmartdataOrdinarySession;
4010
+ /** Client-side deadline for the call's database work (1..120000 ms), including model initialization, server selection and pool waits. */
4011
+ timeoutMS?: number;
4012
+ },
3951
4013
  ): Promise<plugins.mongodb.InsertOneResult<{ _id: TSmartdataPrimaryId<T> }>> {
3952
4014
  if ((this as any)[exactPersistencePolicySymbol]) {
3953
4015
  throw new SmartdataPersistenceError(
@@ -3967,6 +4029,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
3967
4029
  'Model insert requires a newly constructed document.',
3968
4030
  );
3969
4031
  }
4032
+ const timeoutMS = requireBoundedInteger(opts?.timeoutMS, 'Model insert timeoutMS', 120_000);
3970
4033
  assertUsableIdentityValues(
3971
4034
  documentArg as unknown as Record<string, unknown>,
3972
4035
  (this.prototype as { uniqueIndexes?: string[] }).uniqueIndexes || [],
@@ -3980,7 +4043,10 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
3980
4043
  documentArg._updatedAt = now;
3981
4044
  }
3982
4045
  const collection: SmartdataCollection<T> = (this as any).collection;
3983
- const result = await collection.insert(documentArg as any, opts);
4046
+ const result = await collection.insert(documentArg as any, {
4047
+ session: opts?.session,
4048
+ timeoutMS,
4049
+ });
3984
4050
  if (!isOrdinarySessionInTransaction(opts?.session)) {
3985
4051
  documentArg.creationStatus = 'db';
3986
4052
  }
@@ -3998,7 +4064,12 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
3998
4064
  public static async insertMany<T extends SmartDataDbDoc<any, any>>(
3999
4065
  this: plugins.tsclass.typeFest.Class<T>,
4000
4066
  documentsArg: T[],
4001
- opts?: { session?: TSmartdataOrdinarySession; ordered?: boolean },
4067
+ opts?: {
4068
+ session?: TSmartdataOrdinarySession;
4069
+ ordered?: boolean;
4070
+ /** Client-side deadline for the call's database work (1..120000 ms), including model initialization, server selection and pool waits. */
4071
+ timeoutMS?: number;
4072
+ },
4002
4073
  ): Promise<plugins.mongodb.InsertManyResult<{ _id: TSmartdataPrimaryId<T> }>> {
4003
4074
  if ((this as any)[exactPersistencePolicySymbol]) {
4004
4075
  throw new SmartdataPersistenceError(
@@ -4021,6 +4092,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
4021
4092
  'Model insertMany ordered must be a boolean.',
4022
4093
  );
4023
4094
  }
4095
+ const timeoutMS = requireBoundedInteger(opts?.timeoutMS, 'Model insertMany timeoutMS', 120_000);
4024
4096
  const seenDocuments = new Set<T>();
4025
4097
  for (const documentArg of documentsArg) {
4026
4098
  if (!(documentArg instanceof this)) {
@@ -4073,7 +4145,11 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
4073
4145
  }
4074
4146
  };
4075
4147
  try {
4076
- const result = await collection.insertMany(documentsArg as any, opts);
4148
+ const result = await collection.insertMany(documentsArg as any, {
4149
+ session: opts?.session,
4150
+ ordered: opts?.ordered,
4151
+ timeoutMS,
4152
+ });
4077
4153
  markInsertedDocuments(
4078
4154
  result.insertedIds as unknown as Record<number, unknown>,
4079
4155
  );
@@ -4193,6 +4269,11 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
4193
4269
  const normalizedOptions = '$expr' in normalizedFilter
4194
4270
  ? normalizeAtomicExpressionUpdateOptions(opts)
4195
4271
  : opts;
4272
+ const timeoutMS = requireBoundedInteger(
4273
+ normalizedOptions?.timeoutMS,
4274
+ 'Atomic update timeoutMS',
4275
+ 120_000,
4276
+ );
4196
4277
  if (normalizedOptions?.upsert === true && '$expr' in normalizedFilter) {
4197
4278
  throw new SmartdataPersistenceError(
4198
4279
  'invalid_argument',
@@ -4242,7 +4323,11 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
4242
4323
  collection,
4243
4324
  mapIdentityFilterToDocumentId(normalizedFilter, identityDocumentIdField),
4244
4325
  normalizedUpdate,
4245
- normalizedOptions,
4326
+ {
4327
+ upsert: normalizedOptions?.upsert,
4328
+ session: normalizedOptions?.session,
4329
+ timeoutMS,
4330
+ },
4246
4331
  );
4247
4332
  return {
4248
4333
  acknowledged: result.acknowledged,
@@ -4346,6 +4431,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
4346
4431
  upsert: normalizedOptions.upsert,
4347
4432
  ...(normalizedSort ? { sort: normalizedSort as Record<string, 1 | -1> } : {}),
4348
4433
  ...(normalizedOptions.session ? { session: normalizedOptions.session } : {}),
4434
+ timeoutMS: normalizedOptions.timeoutMS,
4349
4435
  },
4350
4436
  );
4351
4437
  if (result.document === null) {
@@ -4473,6 +4559,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
4473
4559
  {
4474
4560
  returnDocument: normalizedOptions.returnDocument,
4475
4561
  ...(normalizedOptions.session ? { session: normalizedOptions.session } : {}),
4562
+ timeoutMS: normalizedOptions.timeoutMS,
4476
4563
  },
4477
4564
  );
4478
4565
  if (result.status === 'not_matched') {
@@ -4550,7 +4637,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
4550
4637
  public static async atomicDeleteMany<T>(
4551
4638
  this: plugins.tsclass.typeFest.Class<T>,
4552
4639
  filterArg: TSmartdataAtomicFilter<T>,
4553
- opts?: ISmartdataAtomicDeleteOptions,
4640
+ opts?: ISmartdataAtomicDeleteManyOptions,
4554
4641
  ): Promise<ISmartdataAtomicDeleteResult> {
4555
4642
  if ((this as any)[exactPersistencePolicySymbol]) {
4556
4643
  throw new SmartdataPersistenceError(
@@ -4582,7 +4669,11 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
4582
4669
  normalizedFilter,
4583
4670
  (this as any).getIdentityDocumentIdField() as string | undefined,
4584
4671
  ),
4585
- { session: opts?.session, timeoutMS: requireBoundedInteger(opts?.timeoutMS, 'Atomic delete-many timeoutMS', 120_000) },
4672
+ {
4673
+ session: opts?.session,
4674
+ timeoutMS: requireBoundedInteger(opts?.timeoutMS, 'Atomic delete-many timeoutMS', 120_000),
4675
+ signal: requireOperationSignal(opts?.signal, 'Atomic delete-many signal'),
4676
+ },
4586
4677
  );
4587
4678
  return {
4588
4679
  acknowledged: result.acknowledged,
@@ -4622,6 +4713,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
4622
4713
  'Atomic update-many does not support $setOnInsert.',
4623
4714
  );
4624
4715
  }
4716
+ const timeoutMS = requireBoundedInteger(opts?.timeoutMS, 'Atomic update-many timeoutMS', 120_000);
4625
4717
  const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
4626
4718
  const declaredAtomicPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
4627
4719
  const uniqueRoots = (this as any).getDeclaredUniqueRoots() as Set<string>;
@@ -4660,7 +4752,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
4660
4752
  (this as any).getIdentityDocumentIdField() as string | undefined,
4661
4753
  ),
4662
4754
  normalizedUpdate,
4663
- opts,
4755
+ { session: opts?.session, timeoutMS },
4664
4756
  );
4665
4757
  return {
4666
4758
  acknowledged: result.acknowledged,
@@ -5096,12 +5188,23 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5096
5188
  public static async getInstances<T>(
5097
5189
  this: plugins.tsclass.typeFest.Class<T>,
5098
5190
  filterArg: MongoFilter<T>,
5099
- opts?: { session?: TSmartdataOrdinarySession }
5191
+ opts?: {
5192
+ session?: TSmartdataOrdinarySession;
5193
+ /**
5194
+ * Client-side deadline for the read (1..120000 ms), from model
5195
+ * initialization to the cursor's last reply. A timed-out `getMore` is
5196
+ * followed by the driver's `killCursors` on a fresh deadline of the same
5197
+ * length, so a read that needs more than one batch settles within about
5198
+ * twice `timeoutMS` against a stalled database.
5199
+ */
5200
+ timeoutMS?: number;
5201
+ }
5100
5202
  ): Promise<T[]> {
5203
+ const timeoutMS = requireBoundedInteger(opts?.timeoutMS, 'timeoutMS', 120_000);
5101
5204
  // Pass session through to findAll for transactional queries
5102
5205
  const foundDocs = await (this as any).collection.findAll(
5103
5206
  (this as any).normalizeReadFilter(filterArg),
5104
- { session: opts?.session },
5207
+ { session: opts?.session, timeoutMS },
5105
5208
  );
5106
5209
  const returnArray: T[] = [];
5107
5210
  for (const foundDoc of foundDocs) {
@@ -5135,6 +5238,14 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5135
5238
  /** Also count all filter matches (extra query). */
5136
5239
  withTotal?: boolean;
5137
5240
  session?: TSmartdataOrdinarySession;
5241
+ /**
5242
+ * Client-side deadline for the page and its optional count together
5243
+ * (1..120000 ms). A page that needs more than one batch can settle
5244
+ * within about twice `timeoutMS` against a stalled database: the
5245
+ * driver's `killCursors` after a timed-out `getMore` gets a fresh
5246
+ * deadline of the same length.
5247
+ */
5248
+ timeoutMS?: number;
5138
5249
  },
5139
5250
  ): Promise<{
5140
5251
  documents: T[];
@@ -5149,6 +5260,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5149
5260
  ? optionsArg.limit!
5150
5261
  : 100;
5151
5262
  const limit = Math.min(requestedLimit, 1000);
5263
+ const timeoutMS = requireBoundedInteger(optionsArg.timeoutMS, 'timeoutMS', 120_000);
5152
5264
  const baseSelector = (this as any).normalizeReadFilter(optionsArg.filter || {});
5153
5265
 
5154
5266
  let selector: any = baseSelector;
@@ -5172,10 +5284,11 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5172
5284
  ordinaryWrite: false,
5173
5285
  prepared: collection.isInitializedForCurrentDatabase(),
5174
5286
  collectionName: collection.collectionName,
5175
- }, async (rawSessionArg) => {
5176
- await collection.init();
5287
+ timeoutMS,
5288
+ }, async (rawSessionArg, deadlineArg) => {
5289
+ await deadlineArg.join(collection.init());
5177
5290
  const rawCursor = collection.mongoDbCollection
5178
- .find(selector, { session: rawSessionArg })
5291
+ .find(selector, { session: rawSessionArg, ...deadlineArg.cursorOptions() })
5179
5292
  .sort({ [optionsArg.sortField]: sortDirection, [uniqueField]: sortDirection })
5180
5293
  .limit(limit + 1);
5181
5294
  return runCursorOperation(rawCursor, async (cursorArg) => {
@@ -5189,6 +5302,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5189
5302
  const total = optionsArg.withTotal
5190
5303
  ? await collection.mongoDbCollection.countDocuments(baseSelector, {
5191
5304
  session: rawSessionArg,
5305
+ ...deadlineArg.commandOptions(),
5192
5306
  })
5193
5307
  : undefined;
5194
5308
  return {
@@ -5203,7 +5317,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5203
5317
  : {}),
5204
5318
  ...(total !== undefined ? { total } : {}),
5205
5319
  };
5206
- });
5320
+ }, deadlineArg.signal);
5207
5321
  });
5208
5322
  }
5209
5323
 
@@ -5234,6 +5348,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5234
5348
  'maxTimeMS',
5235
5349
  120_000,
5236
5350
  ),
5351
+ timeoutMS: requireBoundedInteger(opts?.timeoutMS, 'timeoutMS', 120_000),
5237
5352
  session: opts?.session,
5238
5353
  },
5239
5354
  );
@@ -5250,6 +5365,8 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5250
5365
  filterArg: MongoFilter<T>,
5251
5366
  opts?: {
5252
5367
  maxTimeMS?: number;
5368
+ /** Client-side deadline for the call's database work (1..120000 ms), including model initialization, server selection and pool waits. */
5369
+ timeoutMS?: number;
5253
5370
  session?: TSmartdataOrdinarySession;
5254
5371
  },
5255
5372
  ): Promise<boolean> {
@@ -5262,6 +5379,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5262
5379
  'maxTimeMS',
5263
5380
  120_000,
5264
5381
  ),
5382
+ timeoutMS: requireBoundedInteger(opts?.timeoutMS, 'timeoutMS', 120_000),
5265
5383
  session: opts?.session,
5266
5384
  },
5267
5385
  );
@@ -5406,10 +5524,9 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5406
5524
  'Offset pages require sort, limit and an integer skip from 0 through 2147483647.',
5407
5525
  );
5408
5526
  }
5409
- const timeoutMS = requireBoundedInteger(opts?.timeoutMS === undefined ? 5_000 : opts.timeoutMS, 'timeoutMS', 120_000);
5527
+ const timeoutMS = requireBoundedInteger(opts?.timeoutMS, 'timeoutMS', 120_000);
5410
5528
  const maxTimeMS = requireBoundedInteger(opts?.maxTimeMS, 'maxTimeMS', 120_000);
5411
5529
  const filter = (this as any).normalizeReadFilter(filterArg);
5412
- opts.signal?.throwIfAborted();
5413
5530
  const collection: SmartdataCollection<T> = (this as any).collection;
5414
5531
  if (!collection.isInitializedForCurrentDatabase()) {
5415
5532
  throw new SmartdataPersistenceError(
@@ -5420,18 +5537,20 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5420
5537
  return runWithOrdinarySmartdataSession(opts.session, collection.smartdataDb, {
5421
5538
  ordinaryWrite: false,
5422
5539
  prepared: true,
5423
- }, async (rawSessionArg) => {
5424
- opts.signal?.throwIfAborted();
5540
+ timeoutMS,
5541
+ defaultTimeoutMS: 5_000,
5542
+ signal: opts.signal,
5543
+ }, async (rawSessionArg, deadlineArg) => {
5425
5544
  const rawCursor = collection.mongoDbCollection.find(filter, {
5426
- projection, sort, hint, skip, limit, maxTimeMS, timeoutMS,
5427
- timeoutMode: 'cursorLifetime', session: rawSessionArg, signal: opts.signal,
5545
+ projection, sort, hint, skip, limit, maxTimeMS,
5546
+ ...deadlineArg.cursorOptions(), session: rawSessionArg,
5428
5547
  });
5429
5548
  return runCursorOperation(rawCursor, async (cursorArg) => {
5430
5549
  const documents = await cursorArg.toArray();
5431
5550
  return documents.map((documentArg) =>
5432
5551
  (this as any).createInstanceFromMongoDbNativeDoc(documentArg) as T,
5433
5552
  );
5434
- }, opts.signal);
5553
+ }, deadlineArg.signal);
5435
5554
  });
5436
5555
  }
5437
5556
 
@@ -5778,7 +5897,11 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5778
5897
  * handle from `db.createSession()` — leased for the one write, like every
5779
5898
  * other model API — or a raw driver session.
5780
5899
  */
5781
- public async save(opts?: { session?: TSmartdataOrdinarySession }) {
5900
+ public async save(opts?: {
5901
+ session?: TSmartdataOrdinarySession;
5902
+ /** Client-side deadline for the write (1..120000 ms); `beforeSave()` and `afterSave()` run outside it. */
5903
+ timeoutMS?: number;
5904
+ }) {
5782
5905
  if (getOrdinaryPersistencePolicy(this.constructor)) {
5783
5906
  throw new SmartdataPersistenceError('unsupported_operation',
5784
5907
  'Validated ordinary models use insert() or transactional postimage updates.');
@@ -5792,6 +5915,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5792
5915
  }
5793
5916
  throw new Error('save() is unavailable for exact-persistence models.');
5794
5917
  }
5918
+ const timeoutMS = requireBoundedInteger(opts?.timeoutMS, 'Model save timeoutMS', 120_000);
5795
5919
  // allow hook before saving
5796
5920
  if (typeof (this as any).beforeSave === 'function') {
5797
5921
  await (this as any).beforeSave();
@@ -5809,10 +5933,10 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5809
5933
  // perform insert or update
5810
5934
  switch (this.creationStatus) {
5811
5935
  case 'db':
5812
- dbResult = await this.getCollectionSafe().update(self, { session: opts?.session });
5936
+ dbResult = await this.getCollectionSafe().update(self, { session: opts?.session, timeoutMS });
5813
5937
  break;
5814
5938
  case 'new':
5815
- dbResult = await this.getCollectionSafe().insert(self, { session: opts?.session });
5939
+ dbResult = await this.getCollectionSafe().insert(self, { session: opts?.session, timeoutMS });
5816
5940
  this.creationStatus = 'db';
5817
5941
  break;
5818
5942
  default:
@@ -5959,7 +6083,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5959
6083
  $set: setObject,
5960
6084
  ...(Object.keys(unsetObject).length > 0 ? { $unset: unsetObject } : {}),
5961
6085
  },
5962
- { session: options.session },
6086
+ { session: options.session, timeoutMS: options.timeoutMS },
5963
6087
  );
5964
6088
  if (result.matchedCount === 0) {
5965
6089
  // Nothing was written, so the instance keeps the update timestamp the
@@ -5986,7 +6110,11 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5986
6110
  * `session` takes the handle from `db.createSession()` — leased for the one
5987
6111
  * write, like every other model API — or a raw driver session.
5988
6112
  */
5989
- public async delete(opts?: { session?: TSmartdataOrdinarySession }) {
6113
+ public async delete(opts?: {
6114
+ session?: TSmartdataOrdinarySession;
6115
+ /** Client-side deadline for the write (1..120000 ms); `beforeDelete()` and `afterDelete()` run outside it. */
6116
+ timeoutMS?: number;
6117
+ }) {
5990
6118
  const exactPersistencePolicy = (this.constructor as any)[
5991
6119
  exactPersistencePolicySymbol
5992
6120
  ] as { forbiddenDelete?: () => Promise<never> } | undefined;
@@ -5996,12 +6124,13 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
5996
6124
  }
5997
6125
  throw new Error('delete() is forbidden for exact-persistence models.');
5998
6126
  }
6127
+ const timeoutMS = requireBoundedInteger(opts?.timeoutMS, 'Model delete timeoutMS', 120_000);
5999
6128
  // allow hook before deleting
6000
6129
  if (typeof (this as any).beforeDelete === 'function') {
6001
6130
  await (this as any).beforeDelete();
6002
6131
  }
6003
6132
  // perform deletion
6004
- const result = await this.getCollectionSafe().delete(this, { session: opts?.session });
6133
+ const result = await this.getCollectionSafe().delete(this, { session: opts?.session, timeoutMS });
6005
6134
  // allow hook after delete
6006
6135
  if (typeof (this as any).afterDelete === 'function') {
6007
6136
  await (this as any).afterDelete();