@lossless.org/client 1.2.0 → 1.4.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 (50) hide show
  1. package/.smartconfig.json +1 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.losslessorgclient.d.ts +2 -2
  4. package/dist_ts/core/drivers.js +3 -3
  5. package/dist_ts/nosqldb/classes.atomicdelete.js +3 -3
  6. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.d.ts +20 -0
  7. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +45 -2
  8. package/dist_ts/nosqldb/classes.atomicupdate.js +4 -4
  9. package/dist_ts/nosqldb/classes.collection.d.ts +37 -1
  10. package/dist_ts/nosqldb/classes.collection.js +152 -11
  11. package/dist_ts/nosqldb/classes.collectiontopology.d.ts +8 -0
  12. package/dist_ts/nosqldb/classes.collectiontopology.js +14 -3
  13. package/dist_ts/nosqldb/classes.cursor.d.ts +12 -1
  14. package/dist_ts/nosqldb/classes.cursor.js +20 -3
  15. package/dist_ts/nosqldb/classes.doc.d.ts +130 -11
  16. package/dist_ts/nosqldb/classes.doc.js +464 -89
  17. package/dist_ts/nosqldb/classes.namespaceinspection.js +2 -2
  18. package/dist_ts/nosqldb/classes.session.d.ts +15 -1
  19. package/dist_ts/nosqldb/classes.session.js +32 -6
  20. package/dist_ts/objectstorage/classes.directory.js +6 -3
  21. package/dist_ts/sqldb/classes.sqlconnection.d.ts +14 -1
  22. package/dist_ts/sqldb/classes.sqlconnection.js +1 -1
  23. package/dist_ts/sqldb/clickhouse/index.d.ts +3 -0
  24. package/dist_ts/sqldb/clickhouse/index.js +4 -1
  25. package/dist_ts/sqldb/index.d.ts +2 -5
  26. package/dist_ts/sqldb/index.js +3 -5
  27. package/dist_ts/sqldb/interfaces.d.ts +0 -12
  28. package/dist_ts/sqldb/interfaces.js +1 -1
  29. package/dist_ts/sqldb/mariadb/index.d.ts +6 -0
  30. package/dist_ts/sqldb/mariadb/index.js +5 -0
  31. package/package.json +12 -1
  32. package/readme.md +43 -6
  33. package/ts/00_commitinfo_data.ts +1 -1
  34. package/ts/classes.losslessorgclient.ts +2 -2
  35. package/ts/core/drivers.ts +2 -2
  36. package/ts/nosqldb/classes.atomicdelete.ts +2 -4
  37. package/ts/nosqldb/classes.atomicfindoneandupdate.ts +69 -2
  38. package/ts/nosqldb/classes.atomicupdate.ts +3 -6
  39. package/ts/nosqldb/classes.collection.ts +240 -8
  40. package/ts/nosqldb/classes.collectiontopology.ts +31 -1
  41. package/ts/nosqldb/classes.cursor.ts +22 -2
  42. package/ts/nosqldb/classes.doc.ts +769 -130
  43. package/ts/nosqldb/classes.namespaceinspection.ts +1 -1
  44. package/ts/nosqldb/classes.session.ts +52 -13
  45. package/ts/objectstorage/classes.directory.ts +5 -2
  46. package/ts/sqldb/classes.sqlconnection.ts +14 -1
  47. package/ts/sqldb/clickhouse/index.ts +5 -0
  48. package/ts/sqldb/index.ts +2 -5
  49. package/ts/sqldb/interfaces.ts +0 -12
  50. package/ts/sqldb/mariadb/index.ts +7 -0
@@ -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,
@@ -45,7 +45,8 @@ interface ISmartdataSessionAcquireOptions {
45
45
  interface IOrdinarySessionOperationOptions {
46
46
  ordinaryWrite?: boolean;
47
47
  prepared?: boolean;
48
- preparationMessage?: string;
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
- export const runWithOrdinarySmartdataSession = async <TResult>(
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
- operationArg: (
318
- rawSessionArg: plugins.mongodb.ClientSession | undefined,
319
- ) => Promise<TResult>,
320
- ): Promise<TResult> => {
346
+ ): {
347
+ rawSession: plugins.mongodb.ClientSession | undefined;
348
+ release: () => void;
349
+ } => {
321
350
  if (!isSmartdataSession(sessionArg)) {
322
- return operationArg(sessionArg);
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
- throw new SmartdataPersistenceError(
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
- const lease = acquireSmartdataSession(sessionArg, ownerArg, {
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 {
@@ -258,8 +258,11 @@ export class Directory {
258
258
  if (optionsArg.getEmptyDirectory || optionsArg.createWithInitializerFile) {
259
259
  returnDirectory = new Directory(this.bucketRef, directoryArg, dirNameToSearch);
260
260
  }
261
- if (isFinalDirectory && optionsArg.createWithInitializerFile) {
262
- returnDirectory?.createEmptyFile('00init.txt');
261
+ if (isFinalDirectory && optionsArg.createWithInitializerFile && returnDirectory) {
262
+ // The initializer file is what makes an empty directory exist in object storage, so the caller
263
+ // must not receive the directory before that write completed. An unawaited write is owned by
264
+ // nobody: closing the SmartBucket aborts it and its rejection reaches no caller.
265
+ await returnDirectory.createEmptyFile('00init.txt');
263
266
  }
264
267
  return returnDirectory || null;
265
268
  };
@@ -5,7 +5,20 @@ import { Operation } from '../core/classes.operation.js';
5
5
  import { LosslessClientError, backendError } from '../core/classes.error.js';
6
6
  import type { ICapabilities, IOperationOptions, IReadiness } from '../core/interfaces.js';
7
7
  import { quoteIdentifier, resultLimits, valueBytes } from './interfaces.js';
8
- import type { IInsertOptions, IQueryOptions, ISqlConnectionOptions, ISqlMutationResult, ISqlStatement, TSqlValue } from './interfaces.js';
8
+ import type { IInsertOptions, IQueryOptions, ISqlMutationResult, ISqlStatement, TSqlValue } from './interfaces.js';
9
+
10
+ /** Options of a MariaDB-protocol connection. `tls` is the driver's own TLS configuration. */
11
+ export interface ISqlConnectionOptions {
12
+ backend: 'sqldb' | 'mariadb';
13
+ host: string;
14
+ port?: number;
15
+ user: string;
16
+ password: string;
17
+ database: string;
18
+ connectionLimit?: number;
19
+ timeoutMs?: number;
20
+ tls?: plugins.mariadb.ConnectionConfig['ssl'];
21
+ }
9
22
 
10
23
  interface ILease {
11
24
  connection: SqlPooledConnection;
@@ -1,3 +1,8 @@
1
+ // Connection
2
+ export { ClickHouseConnection } from '../classes.clickhouseconnection.js';
3
+ export { quoteClickHouseIdentifier } from '../interfaces.js';
4
+ export type { IClickHouseConnectionOptions, IClickHouseStatement, IInsertOptions, IQueryOptions } from '../interfaces.js';
5
+
1
6
  // Core
2
7
  export * from './smartclickhouse.classes.smartclickhouse.js';
3
8
  export * from './smartclickhouse.classes.clickhousetable.js';
package/ts/sqldb/index.ts CHANGED
@@ -1,6 +1,3 @@
1
- export { SqlConnection, SqlTransaction } from './classes.sqlconnection.js';
2
- export { ClickHouseConnection } from './classes.clickhouseconnection.js';
3
- export { SqlTable } from './classes.sqltable.js';
4
- export type { ISqlSelectOptions, TSqlSelector } from './classes.sqltable.js';
5
- export * from './interfaces.js';
1
+ export * from './mariadb/index.js';
6
2
  export * from './clickhouse/index.js';
3
+ export { resultLimits, valueBytes } from './interfaces.js';
@@ -1,21 +1,9 @@
1
- import type * as plugins from './plugins.mariadb.js';
2
1
  import type { IOperationOptions } from '../core/interfaces.js';
3
2
  import { LosslessClientError } from '../core/classes.error.js';
4
3
 
5
4
  export type TSqlValue = null | string | number | bigint | boolean | Date | Uint8Array;
6
5
  export interface ISqlStatement { sql: string; values?: readonly TSqlValue[] }
7
6
  export interface IQueryOptions extends IOperationOptions { maxRows?: number; maxBytes?: number }
8
- export interface ISqlConnectionOptions {
9
- backend: 'sqldb' | 'mariadb';
10
- host: string;
11
- port?: number;
12
- user: string;
13
- password: string;
14
- database: string;
15
- connectionLimit?: number;
16
- timeoutMs?: number;
17
- tls?: plugins.mariadb.ConnectionConfig['ssl'];
18
- }
19
7
  export interface ISqlMutationResult {
20
8
  affectedRows: number;
21
9
  insertId?: bigint;
@@ -0,0 +1,7 @@
1
+ // MariaDB-protocol entry point: SQLDB and MariaDB connections without the ClickHouse driver.
2
+ export { SqlConnection, SqlTransaction } from '../classes.sqlconnection.js';
3
+ export type { ISqlConnectionOptions } from '../classes.sqlconnection.js';
4
+ export { SqlTable } from '../classes.sqltable.js';
5
+ export type { ISqlSelectOptions, TSqlSelector } from '../classes.sqltable.js';
6
+ export { quoteIdentifier } from '../interfaces.js';
7
+ export type { IInsertOptions, IQueryOptions, ISqlMutationResult, ISqlStatement, TSqlValue } from '../interfaces.js';