@lossless.org/client 1.1.0 → 1.3.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/.smartconfig.json +1 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/nosqldb/classes.atomicdelete.js +3 -3
- package/dist_ts/nosqldb/classes.atomicfindoneandupdate.d.ts +20 -0
- package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +45 -2
- package/dist_ts/nosqldb/classes.atomicupdate.js +4 -4
- package/dist_ts/nosqldb/classes.collection.d.ts +37 -1
- package/dist_ts/nosqldb/classes.collection.js +235 -30
- package/dist_ts/nosqldb/classes.collectiontopology.d.ts +19 -0
- package/dist_ts/nosqldb/classes.collectiontopology.js +29 -3
- package/dist_ts/nosqldb/classes.cursor.d.ts +12 -1
- package/dist_ts/nosqldb/classes.cursor.js +20 -3
- package/dist_ts/nosqldb/classes.doc.d.ts +138 -11
- package/dist_ts/nosqldb/classes.doc.js +490 -91
- package/dist_ts/nosqldb/classes.exactpersistence.js +10 -2
- package/dist_ts/nosqldb/classes.namespaceinspection.js +2 -2
- package/dist_ts/nosqldb/classes.persistence.d.ts +6 -0
- package/dist_ts/nosqldb/classes.persistence.js +13 -1
- package/dist_ts/nosqldb/classes.session.d.ts +15 -1
- package/dist_ts/nosqldb/classes.session.js +32 -6
- package/package.json +4 -1
- package/readme.md +38 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/nosqldb/classes.atomicdelete.ts +2 -4
- package/ts/nosqldb/classes.atomicfindoneandupdate.ts +69 -2
- package/ts/nosqldb/classes.atomicupdate.ts +3 -6
- package/ts/nosqldb/classes.collection.ts +372 -35
- package/ts/nosqldb/classes.collectiontopology.ts +56 -1
- package/ts/nosqldb/classes.cursor.ts +22 -2
- package/ts/nosqldb/classes.doc.ts +819 -132
- package/ts/nosqldb/classes.exactpersistence.ts +9 -1
- package/ts/nosqldb/classes.namespaceinspection.ts +1 -1
- package/ts/nosqldb/classes.persistence.ts +17 -0
- package/ts/nosqldb/classes.session.ts +52 -13
|
@@ -4,6 +4,7 @@ import { assertBsonWithoutMutation } from './classes.bsonassertion.js';
|
|
|
4
4
|
import { getOrdinaryPersistencePolicy } from './classes.ordinarypersistence.js';
|
|
5
5
|
|
|
6
6
|
import type { SmartdataCollection } from './classes.collection.js';
|
|
7
|
+
import { getIdentityIndexName } from './classes.collectiontopology.js';
|
|
7
8
|
import { registerCollectionReconnectHandler } from './classes.collectionlifecycle.js';
|
|
8
9
|
import type { SmartdataSession } from './classes.session.js';
|
|
9
10
|
import { acquireSmartdataSession } from './classes.session.js';
|
|
@@ -1024,10 +1025,17 @@ export class SmartdataExactCollection<
|
|
|
1024
1025
|
const missingUniqueIndexes = this.uniqueIndexes.filter(
|
|
1025
1026
|
(uniqueIndexArg) => !this.collection.uniqueIndexes.includes(uniqueIndexArg),
|
|
1026
1027
|
);
|
|
1028
|
+
const boundSchema = this.collection.getBoundModelSchema();
|
|
1027
1029
|
for (const uniqueIndex of missingUniqueIndexes) {
|
|
1028
1030
|
await targetCollectionArg.createIndex(
|
|
1029
1031
|
{ [uniqueIndex]: 1 },
|
|
1030
|
-
{
|
|
1032
|
+
{
|
|
1033
|
+
unique: true,
|
|
1034
|
+
// A model may back its identity with a differently named index;
|
|
1035
|
+
// creating `<field>_1` would duplicate that key.
|
|
1036
|
+
name: getIdentityIndexName(boundSchema, uniqueIndex)
|
|
1037
|
+
?? `${uniqueIndex}_1`,
|
|
1038
|
+
},
|
|
1031
1039
|
);
|
|
1032
1040
|
}
|
|
1033
1041
|
|
|
@@ -173,7 +173,7 @@ export const inspectSmartdataNamespace = async (
|
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
const topology = inspectCollectionTopologySnapshot({
|
|
176
|
-
collectionName: namespace.name, indexes: [],
|
|
176
|
+
collectionName: namespace.name, indexes: [], toleratedIndexNames: [],
|
|
177
177
|
}, { namespace: namespace as unknown as Record<string, unknown>, indexes });
|
|
178
178
|
collections.push(Object.freeze({
|
|
179
179
|
collectionName: namespace.name, namespaceType, documentCount,
|
|
@@ -43,6 +43,23 @@ export const isMongoDuplicateKeyError = (
|
|
|
43
43
|
);
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* MongoDB refuses an index whose key or name collides with an existing one:
|
|
48
|
+
* `IndexOptionsConflict` when an equivalent index already carries another
|
|
49
|
+
* name, `IndexKeySpecsConflict` when the name is taken by another key.
|
|
50
|
+
*/
|
|
51
|
+
export const isMongoIndexConflictError = (
|
|
52
|
+
errorArg: unknown,
|
|
53
|
+
): errorArg is plugins.mongodb.MongoServerError => {
|
|
54
|
+
return (
|
|
55
|
+
errorArg instanceof plugins.mongodb.MongoServerError &&
|
|
56
|
+
(errorArg.code === 85 ||
|
|
57
|
+
errorArg.code === 86 ||
|
|
58
|
+
errorArg.codeName === 'IndexOptionsConflict' ||
|
|
59
|
+
errorArg.codeName === 'IndexKeySpecsConflict')
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
46
63
|
export const normalizeOrdinaryPersistenceError = (
|
|
47
64
|
errorArg: unknown,
|
|
48
65
|
messageArg: string,
|
|
@@ -45,7 +45,8 @@ interface ISmartdataSessionAcquireOptions {
|
|
|
45
45
|
interface IOrdinarySessionOperationOptions {
|
|
46
46
|
ordinaryWrite?: boolean;
|
|
47
47
|
prepared?: boolean;
|
|
48
|
-
|
|
48
|
+
/** Names the collection whose preparation a refusal must ask for. */
|
|
49
|
+
collectionName?: string;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
const transactionContext = new plugins.nodeAsyncHooks.AsyncLocalStorage<
|
|
@@ -310,27 +311,65 @@ export const isOrdinarySessionInTransaction = (
|
|
|
310
311
|
return sessionArg.inTransaction();
|
|
311
312
|
};
|
|
312
313
|
|
|
313
|
-
|
|
314
|
+
/**
|
|
315
|
+
* A SmartData model initializes its collection lazily, and that initialization
|
|
316
|
+
* issues index DDL outside any session. Under an open transaction on the same
|
|
317
|
+
* collection the DDL waits for the transaction while the transaction waits for
|
|
318
|
+
* this operation, so the caller would only learn about it when the client
|
|
319
|
+
* deadline expires. Naming the missing preparation is the whole remedy.
|
|
320
|
+
*/
|
|
321
|
+
const refuseUnpreparedCollection = (
|
|
322
|
+
optionsArg: IOrdinarySessionOperationOptions,
|
|
323
|
+
reasonArg: 'with an owned SmartData session' | 'inside a transaction',
|
|
324
|
+
): never => {
|
|
325
|
+
throw new SmartdataPersistenceError(
|
|
326
|
+
'unsupported_operation',
|
|
327
|
+
optionsArg.collectionName
|
|
328
|
+
? `Initialize collection "${optionsArg.collectionName}" before using it ${reasonArg}.`
|
|
329
|
+
: `Initialize every participating SmartData model before using it ${reasonArg}.`,
|
|
330
|
+
);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Takes the same lease `runWithOrdinarySmartdataSession` takes, but hands it to
|
|
335
|
+
* the caller instead of releasing it at the end of one operation. Resources
|
|
336
|
+
* that outlive their creating call — a cursor the caller iterates itself — hold
|
|
337
|
+
* the owned session until they are closed, so a second operation on that
|
|
338
|
+
* session is refused while the cursor is open, exactly as the driver requires.
|
|
339
|
+
* A raw `ClientSession` or no session keeps its pass-through behavior and gets
|
|
340
|
+
* an inert release. `release()` is idempotent.
|
|
341
|
+
*/
|
|
342
|
+
export const leaseOrdinarySmartdataSession = (
|
|
314
343
|
sessionArg: TSmartdataOrdinarySession | undefined,
|
|
315
344
|
ownerArg: SmartdataDb,
|
|
316
345
|
optionsArg: IOrdinarySessionOperationOptions,
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
) =>
|
|
320
|
-
|
|
346
|
+
): {
|
|
347
|
+
rawSession: plugins.mongodb.ClientSession | undefined;
|
|
348
|
+
release: () => void;
|
|
349
|
+
} => {
|
|
321
350
|
if (!isSmartdataSession(sessionArg)) {
|
|
322
|
-
|
|
351
|
+
if (!optionsArg.prepared && sessionArg?.inTransaction()) {
|
|
352
|
+
refuseUnpreparedCollection(optionsArg, 'inside a transaction');
|
|
353
|
+
}
|
|
354
|
+
return { rawSession: sessionArg, release: () => {} };
|
|
323
355
|
}
|
|
324
356
|
if (!optionsArg.prepared) {
|
|
325
|
-
|
|
326
|
-
'unsupported_operation',
|
|
327
|
-
optionsArg.preparationMessage ||
|
|
328
|
-
'Initialize every participating SmartData model before using an owned session.',
|
|
329
|
-
);
|
|
357
|
+
refuseUnpreparedCollection(optionsArg, 'with an owned SmartData session');
|
|
330
358
|
}
|
|
331
|
-
|
|
359
|
+
return acquireSmartdataSession(sessionArg, ownerArg, {
|
|
332
360
|
ordinaryWrite: optionsArg.ordinaryWrite,
|
|
333
361
|
});
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
export const runWithOrdinarySmartdataSession = async <TResult>(
|
|
365
|
+
sessionArg: TSmartdataOrdinarySession | undefined,
|
|
366
|
+
ownerArg: SmartdataDb,
|
|
367
|
+
optionsArg: IOrdinarySessionOperationOptions,
|
|
368
|
+
operationArg: (
|
|
369
|
+
rawSessionArg: plugins.mongodb.ClientSession | undefined,
|
|
370
|
+
) => Promise<TResult>,
|
|
371
|
+
): Promise<TResult> => {
|
|
372
|
+
const lease = leaseOrdinarySmartdataSession(sessionArg, ownerArg, optionsArg);
|
|
334
373
|
try {
|
|
335
374
|
return await operationArg(lease.rawSession);
|
|
336
375
|
} finally {
|