@lossless.org/client 0.1.1 → 1.1.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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@lossless.org/client',
6
- version: '0.1.1',
6
+ version: '1.1.0',
7
7
  description: 'One typed client for NoSQLDB, MongoDB, SQLDB, MariaDB, ClickHouse and S3 object storage.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLDBGQUEwRjtDQUN4RyxDQUFBIn0=
@@ -0,0 +1,17 @@
1
+ import type { TBackend } from './interfaces.js';
2
+ /** Backends whose driver package ships as an optional peer dependency instead of a dependency. */
3
+ export type TOptionalDriverBackend = Extract<TBackend, 'mariadb' | 'clickhouse' | 's3'>;
4
+ export interface IOptionalDriver {
5
+ /** npm package the consumer installs. */
6
+ readonly package: string;
7
+ /** Entry point that needs it, so the refusal names the import the consumer already wrote. */
8
+ readonly subpath: string;
9
+ /** Connections the driver enables, phrased as the aggregate client configuration names them. */
10
+ readonly usage: string;
11
+ }
12
+ export declare const optionalDrivers: Readonly<Record<TOptionalDriverBackend, IOptionalDriver>>;
13
+ /**
14
+ * Rejection handler for a lazy family import. An absent optional driver becomes an instruction naming the package and
15
+ * the entry point; every other failure keeps its original identity so real load errors stay visible.
16
+ */
17
+ export declare const requireDriver: (backend: TOptionalDriverBackend) => (error: unknown) => never;
@@ -0,0 +1,27 @@
1
+ import { LosslessClientError } from './classes.error.js';
2
+ export const optionalDrivers = Object.freeze({
3
+ mariadb: { package: 'mariadb', subpath: '@lossless.org/client/sqldb',
4
+ usage: 'sqldb connections with backend "sqldb" or "mariadb"' },
5
+ clickhouse: { package: '@clickhouse/client', subpath: '@lossless.org/client/sqldb',
6
+ usage: 'sqldb connections with backend "clickhouse"' },
7
+ s3: { package: '@aws-sdk/client-s3', subpath: '@lossless.org/client/objectstorage',
8
+ usage: 'objectstorage connections with backend "s3"' },
9
+ });
10
+ /** Node names the unresolved package in its message; anything else is a defect of this package, not a missing install. */
11
+ const isUninstalled = (error, packageName) => {
12
+ const details = error;
13
+ return !!details && details.code === 'ERR_MODULE_NOT_FOUND' &&
14
+ typeof details.message === 'string' && details.message.includes(`'${packageName}'`);
15
+ };
16
+ /**
17
+ * Rejection handler for a lazy family import. An absent optional driver becomes an instruction naming the package and
18
+ * the entry point; every other failure keeps its original identity so real load errors stay visible.
19
+ */
20
+ export const requireDriver = (backend) => (error) => {
21
+ const driver = optionalDrivers[backend];
22
+ if (!isUninstalled(error, driver.package))
23
+ throw error;
24
+ throw new LosslessClientError('driver_missing', `Install the optional peer dependency "${driver.package}" to use ${driver.usage} (${driver.subpath}). ` +
25
+ `Run: pnpm add ${driver.package}`, { cause: error, retryability: 'permanent' });
26
+ };
27
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHJpdmVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2NvcmUvZHJpdmVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQWV6RCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQThELE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDdEcsT0FBTyxFQUFFLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsNEJBQTRCO1FBQ2xFLEtBQUssRUFBRSxxREFBcUQsRUFBRTtJQUNoRSxVQUFVLEVBQUUsRUFBRSxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsT0FBTyxFQUFFLDRCQUE0QjtRQUNoRixLQUFLLEVBQUUsNkNBQTZDLEVBQUU7SUFDeEQsRUFBRSxFQUFFLEVBQUUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE9BQU8sRUFBRSxvQ0FBb0M7UUFDaEYsS0FBSyxFQUFFLDZDQUE2QyxFQUFFO0NBQ3pELENBQUMsQ0FBQztBQUVILDBIQUEwSDtBQUMxSCxNQUFNLGFBQWEsR0FBRyxDQUFDLEtBQWMsRUFBRSxXQUFtQixFQUFXLEVBQUU7SUFDckUsTUFBTSxPQUFPLEdBQUcsS0FBaUUsQ0FBQztJQUNsRixPQUFPLENBQUMsQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLElBQUksS0FBSyxzQkFBc0I7UUFDekQsT0FBTyxPQUFPLENBQUMsT0FBTyxLQUFLLFFBQVEsSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxJQUFJLFdBQVcsR0FBRyxDQUFDLENBQUM7QUFDeEYsQ0FBQyxDQUFDO0FBRUY7OztHQUdHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLENBQUMsT0FBK0IsRUFBRSxFQUFFLENBQUMsQ0FBQyxLQUFjLEVBQVMsRUFBRTtJQUMxRixNQUFNLE1BQU0sR0FBRyxlQUFlLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDeEMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLE9BQU8sQ0FBQztRQUFFLE1BQU0sS0FBSyxDQUFDO0lBQ3ZELE1BQU0sSUFBSSxtQkFBbUIsQ0FBQyxnQkFBZ0IsRUFDNUMseUNBQXlDLE1BQU0sQ0FBQyxPQUFPLFlBQVksTUFBTSxDQUFDLEtBQUssS0FBSyxNQUFNLENBQUMsT0FBTyxLQUFLO1FBQ3ZHLGlCQUFpQixNQUFNLENBQUMsT0FBTyxFQUFFLEVBQ2pDLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxZQUFZLEVBQUUsV0FBVyxFQUFFLENBQUMsQ0FBQztBQUNqRCxDQUFDLENBQUMifQ==
@@ -15,4 +15,4 @@ export interface IReadiness {
15
15
  ready: boolean;
16
16
  reason?: string;
17
17
  }
18
- export type TClientErrorCode = 'invalid_argument' | 'unsupported_capability' | 'not_connected' | 'closed' | 'timeout' | 'cancelled' | 'result_limit' | 'authentication' | 'conflict' | 'backend_error' | 'ambiguous_write' | 'partial_write';
18
+ export type TClientErrorCode = 'invalid_argument' | 'unsupported_capability' | 'driver_missing' | 'not_connected' | 'closed' | 'timeout' | 'cancelled' | 'result_limit' | 'authentication' | 'conflict' | 'backend_error' | 'ambiguous_write' | 'partial_write';
@@ -1,4 +1,6 @@
1
1
  export { LosslessOrgClient } from './classes.losslessorgclient.js';
2
2
  export type { ILosslessOrgClientOptions } from './classes.losslessorgclient.js';
3
3
  export { LosslessClientError } from './core/classes.error.js';
4
+ export { optionalDrivers } from './core/drivers.js';
5
+ export type { IOptionalDriver, TOptionalDriverBackend } from './core/drivers.js';
4
6
  export type { ICapabilities, IOperationOptions, IReadiness, TBackend, TCapability, TClientErrorCode } from './core/interfaces.js';
package/dist_ts/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { LosslessOrgClient } from './classes.losslessorgclient.js';
2
2
  export { LosslessClientError } from './core/classes.error.js';
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUVuRSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQyJ9
3
+ export { optionalDrivers } from './core/drivers.js';
4
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUVuRSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUM5RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUJBQW1CLENBQUMifQ==
@@ -12,6 +12,27 @@ export declare const executeAtomicUpdate: <T>(collectionArg: SmartdataCollection
12
12
  upsert?: boolean;
13
13
  session?: TSmartdataOrdinarySession;
14
14
  }) => Promise<plugins.mongodb.UpdateResult>;
15
+ /**
16
+ * Executes a bounded batch of model-validated atomic upserts against MongoDB.
17
+ *
18
+ * This helper is package-internal. `SmartDataDbDoc.atomicUpsertMany()` owns the
19
+ * public API and must validate and normalize every filter and update before
20
+ * calling this raw persistence boundary. Each entry applies atomically; the
21
+ * batch as a whole is not isolated unless the caller supplies a transaction
22
+ * session.
23
+ */
24
+ export declare const executeAtomicUpsertMany: <T>(collectionArg: SmartdataCollection<T>, operationsArg: ReadonlyArray<{
25
+ filter: plugins.mongodb.Filter<plugins.mongodb.Document>;
26
+ update: plugins.mongodb.UpdateFilter<plugins.mongodb.Document>;
27
+ }>, optsArg?: {
28
+ session?: TSmartdataOrdinarySession;
29
+ timeoutMS?: number;
30
+ }) => Promise<{
31
+ acknowledged: boolean;
32
+ matchedCount: number;
33
+ modifiedCount: number;
34
+ upsertedCount: number;
35
+ }>;
15
36
  /**
16
37
  * Executes a model-validated atomic update-many against MongoDB.
17
38
  *
@@ -26,6 +26,46 @@ export const executeAtomicUpdate = async (collectionArg, filterArg, updateArg, o
26
26
  }
27
27
  });
28
28
  };
29
+ /**
30
+ * Executes a bounded batch of model-validated atomic upserts against MongoDB.
31
+ *
32
+ * This helper is package-internal. `SmartDataDbDoc.atomicUpsertMany()` owns the
33
+ * public API and must validate and normalize every filter and update before
34
+ * calling this raw persistence boundary. Each entry applies atomically; the
35
+ * batch as a whole is not isolated unless the caller supplies a transaction
36
+ * session.
37
+ */
38
+ export const executeAtomicUpsertMany = async (collectionArg, operationsArg, optsArg) => {
39
+ return runWithOrdinarySmartdataSession(optsArg?.session, collectionArg.smartdataDb, {
40
+ ordinaryWrite: true,
41
+ prepared: collectionArg.isInitializedForCurrentDatabase(),
42
+ preparationMessage: `Initialize collection "${collectionArg.collectionName}" before using an owned SmartData session.`,
43
+ }, async (rawSessionArg) => {
44
+ await collectionArg.init();
45
+ try {
46
+ const result = await collectionArg.mongoDbCollection.bulkWrite(operationsArg.map((operationArg) => ({
47
+ updateOne: {
48
+ filter: operationArg.filter,
49
+ update: operationArg.update,
50
+ upsert: true,
51
+ },
52
+ })), {
53
+ ordered: false,
54
+ session: rawSessionArg,
55
+ timeoutMS: optsArg?.timeoutMS,
56
+ });
57
+ return {
58
+ acknowledged: result.isOk(),
59
+ matchedCount: result.matchedCount,
60
+ modifiedCount: result.modifiedCount,
61
+ upsertedCount: result.upsertedCount,
62
+ };
63
+ }
64
+ catch (errorArg) {
65
+ return normalizeOrdinaryPersistenceError(errorArg, `Atomic upsert-many conflicts with a unique index in collection "${collectionArg.collectionName}".`);
66
+ }
67
+ });
68
+ };
29
69
  /**
30
70
  * Executes a model-validated atomic update-many against MongoDB.
31
71
  *
@@ -52,4 +92,4 @@ export const executeAtomicUpdateMany = async (collectionArg, filterArg, updateAr
52
92
  }
53
93
  });
54
94
  };
55
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5hdG9taWN1cGRhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9ub3NxbGRiL2NsYXNzZXMuYXRvbWljdXBkYXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFBRSxpQ0FBaUMsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQzdFLE9BQU8sRUFDTCwrQkFBK0IsR0FFaEMsTUFBTSxzQkFBc0IsQ0FBQztBQUU5Qjs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxLQUFLLEVBQ3RDLGFBQXFDLEVBQ3JDLFNBQTJELEVBQzNELFNBQWlFLEVBQ2pFLE9BR0MsRUFDc0MsRUFBRTtJQUN6QyxPQUFPLCtCQUErQixDQUNwQyxPQUFPLEVBQUUsT0FBTyxFQUNoQixhQUFhLENBQUMsV0FBVyxFQUN6QjtRQUNFLGFBQWEsRUFBRSxJQUFJO1FBQ25CLFFBQVEsRUFBRSxhQUFhLENBQUMsK0JBQStCLEVBQUU7UUFDekQsa0JBQWtCLEVBQ2hCLDBCQUEwQixhQUFhLENBQUMsY0FBYyw0Q0FBNEM7S0FDckcsRUFDRCxLQUFLLEVBQUUsYUFBYSxFQUFFLEVBQUU7UUFDdEIsTUFBTSxhQUFhLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDM0IsSUFBSSxDQUFDO1lBQ0gsT0FBTyxNQUFNLGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQyxTQUFTLENBQ3BELFNBQVMsRUFDVCxTQUFTLEVBQ1Q7Z0JBQ0UsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEtBQUssSUFBSTtnQkFDaEMsT0FBTyxFQUFFLGFBQWE7YUFDdkIsQ0FDRixDQUFDO1FBQ0osQ0FBQztRQUFDLE9BQU8sUUFBUSxFQUFFLENBQUM7WUFDbEIsT0FBTyxpQ0FBaUMsQ0FDdEMsUUFBUSxFQUNSLDhEQUE4RCxhQUFhLENBQUMsY0FBYyxJQUFJLENBQy9GLENBQUM7UUFDSixDQUFDO0lBQ0gsQ0FBQyxDQUNGLENBQUM7QUFDSixDQUFDLENBQUM7QUFFRjs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFHLEtBQUssRUFDMUMsYUFBcUMsRUFDckMsU0FBMkQsRUFDM0QsU0FBaUUsRUFDakUsT0FFQyxFQUNzQyxFQUFFO0lBQ3pDLE9BQU8sK0JBQStCLENBQ3BDLE9BQU8sRUFBRSxPQUFPLEVBQ2hCLGFBQWEsQ0FBQyxXQUFXLEVBQ3pCO1FBQ0UsYUFBYSxFQUFFLElBQUk7UUFDbkIsUUFBUSxFQUFFLGFBQWEsQ0FBQywrQkFBK0IsRUFBRTtRQUN6RCxrQkFBa0IsRUFDaEIsMEJBQTBCLGFBQWEsQ0FBQyxjQUFjLDRDQUE0QztLQUNyRyxFQUNELEtBQUssRUFBRSxhQUFhLEVBQUUsRUFBRTtRQUN0QixNQUFNLGFBQWEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUMzQixJQUFJLENBQUM7WUFDSCxPQUFPLE1BQU0sYUFBYSxDQUFDLGlCQUFpQixDQUFDLFVBQVUsQ0FDckQsU0FBUyxFQUNULFNBQVMsRUFDVDtnQkFDRSxPQUFPLEVBQUUsYUFBYTthQUN2QixDQUNGLENBQUM7UUFDSixDQUFDO1FBQUMsT0FBTyxRQUFRLEVBQUUsQ0FBQztZQUNsQixPQUFPLGlDQUFpQyxDQUN0QyxRQUFRLEVBQ1IsbUVBQW1FLGFBQWEsQ0FBQyxjQUFjLElBQUksQ0FDcEcsQ0FBQztRQUNKLENBQUM7SUFDSCxDQUFDLENBQ0YsQ0FBQztBQUNKLENBQUMsQ0FBQyJ9
95
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5hdG9taWN1cGRhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9ub3NxbGRiL2NsYXNzZXMuYXRvbWljdXBkYXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFBRSxpQ0FBaUMsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQzdFLE9BQU8sRUFDTCwrQkFBK0IsR0FFaEMsTUFBTSxzQkFBc0IsQ0FBQztBQUU5Qjs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxLQUFLLEVBQ3RDLGFBQXFDLEVBQ3JDLFNBQTJELEVBQzNELFNBQWlFLEVBQ2pFLE9BR0MsRUFDc0MsRUFBRTtJQUN6QyxPQUFPLCtCQUErQixDQUNwQyxPQUFPLEVBQUUsT0FBTyxFQUNoQixhQUFhLENBQUMsV0FBVyxFQUN6QjtRQUNFLGFBQWEsRUFBRSxJQUFJO1FBQ25CLFFBQVEsRUFBRSxhQUFhLENBQUMsK0JBQStCLEVBQUU7UUFDekQsa0JBQWtCLEVBQ2hCLDBCQUEwQixhQUFhLENBQUMsY0FBYyw0Q0FBNEM7S0FDckcsRUFDRCxLQUFLLEVBQUUsYUFBYSxFQUFFLEVBQUU7UUFDdEIsTUFBTSxhQUFhLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDM0IsSUFBSSxDQUFDO1lBQ0gsT0FBTyxNQUFNLGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQyxTQUFTLENBQ3BELFNBQVMsRUFDVCxTQUFTLEVBQ1Q7Z0JBQ0UsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEtBQUssSUFBSTtnQkFDaEMsT0FBTyxFQUFFLGFBQWE7YUFDdkIsQ0FDRixDQUFDO1FBQ0osQ0FBQztRQUFDLE9BQU8sUUFBUSxFQUFFLENBQUM7WUFDbEIsT0FBTyxpQ0FBaUMsQ0FDdEMsUUFBUSxFQUNSLDhEQUE4RCxhQUFhLENBQUMsY0FBYyxJQUFJLENBQy9GLENBQUM7UUFDSixDQUFD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
@@ -22,6 +22,14 @@ export interface ICollectionBindingOptions {
22
22
  * its legacy collection without changing stored data.
23
23
  */
24
24
  collectionName?: string;
25
+ /**
26
+ * Stores one declared `@unI()` string identity as the document `_id`, making
27
+ * the primary key itself the uniqueness authority. The field keeps its own
28
+ * stored value and receives no separate unique index. Irreversible for an
29
+ * existing collection: every stored document must already satisfy
30
+ * `_id === document[field]`.
31
+ */
32
+ identityAsDocumentId?: string;
25
33
  }
26
34
  export type TCollectionModelIndexDirection = 1 | -1 | 'text';
27
35
  export interface ICollectionModelIndexOptions {
@@ -63,6 +71,13 @@ export interface ICollectionModelConfig<TModel extends object = any> {
63
71
  * ascending unique index in `indexes`.
64
72
  */
65
73
  identityFields?: ReadonlyArray<TStringFieldKey<TModel>>;
74
+ /**
75
+ * Stores one declared string identity as the document `_id`. The primary key
76
+ * becomes the uniqueness authority, so the field must not declare its own
77
+ * unique index. Irreversible for an existing collection: every stored
78
+ * document must already satisfy `_id === document[field]`.
79
+ */
80
+ identityAsDocumentId?: TStringFieldKey<TModel>;
66
81
  /**
67
82
  * Persisted fields used by SmartData's search helpers.
68
83
  */
@@ -78,6 +93,8 @@ export interface INormalizedCollectionModelSchema {
78
93
  readonly persistedFields: readonly string[];
79
94
  readonly numericFields: readonly string[];
80
95
  readonly identityFields: readonly string[];
96
+ /** Declared identity field stored as the document `_id`, if any. */
97
+ readonly identityAsDocumentId?: string;
81
98
  readonly identityValueTypes: Readonly<Record<string, TSmartdataIdentityValueType>>;
82
99
  readonly searchableFields: readonly string[];
83
100
  readonly indexes: ReadonlyArray<{
@@ -111,6 +128,11 @@ type TCollectionModelConstructor<TModel extends SmartDataDbDoc<any, any>> = {
111
128
  name: string;
112
129
  prototype: TModel;
113
130
  };
131
+ /**
132
+ * Returns the declared identity field a model stores as its document `_id`.
133
+ * Reading the bound schema resolver never resolves a database.
134
+ */
135
+ export declare const getIdentityDocumentIdField: (modelArg: any) => string | undefined;
114
136
  /**
115
137
  * Programmatically declares and binds an ordinary SmartData model.
116
138
  *
@@ -229,6 +251,15 @@ export declare class SmartdataCollection<T> {
229
251
  * create an object in the database
230
252
  */
231
253
  private prepareOrdinaryInsert;
254
+ /**
255
+ * Keys a stored document by its declared identity when the model opted into
256
+ * `identityAsDocumentId`. The identity keeps its own stored field, but reads
257
+ * address `_id`, so a document written before the option was declared is
258
+ * reachable only if its `_id` already equals that identity.
259
+ */
260
+ private applyIdentityDocumentId;
261
+ /** True when stored documents need SmartData-owned preparation before a write. */
262
+ private get preparesStoredDocuments();
232
263
  insert(dbDocArg: T & SmartDataDbDoc<T, unknown>, opts?: {
233
264
  session?: TSmartdataOrdinarySession;
234
265
  }): Promise<any>;