@stonyx/orm 0.3.2-beta.33 → 0.3.2-beta.330

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 (71) hide show
  1. package/README.md +377 -30
  2. package/config/environment.js +111 -12
  3. package/dist/column-conversion.d.ts +34 -0
  4. package/dist/column-conversion.js +34 -0
  5. package/dist/commands.js +34 -0
  6. package/dist/dynamodb/connection.d.ts +31 -0
  7. package/dist/dynamodb/connection.js +28 -0
  8. package/dist/dynamodb/dynamodb-db.d.ts +142 -0
  9. package/dist/dynamodb/dynamodb-db.js +596 -0
  10. package/dist/dynamodb/operation-builder.d.ts +76 -0
  11. package/dist/dynamodb/operation-builder.js +116 -0
  12. package/dist/dynamodb/type-map.d.ts +31 -0
  13. package/dist/dynamodb/type-map.js +48 -0
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.js +3 -2
  16. package/dist/main.d.ts +1 -0
  17. package/dist/main.js +29 -1
  18. package/dist/manage-record.d.ts +4 -0
  19. package/dist/manage-record.js +95 -34
  20. package/dist/mysql/connection.d.ts +3 -0
  21. package/dist/mysql/mysql-db.d.ts +32 -2
  22. package/dist/mysql/mysql-db.js +239 -45
  23. package/dist/mysql/query-builder.d.ts +1 -0
  24. package/dist/mysql/query-builder.js +17 -0
  25. package/dist/normalize-record-id.d.ts +67 -0
  26. package/dist/normalize-record-id.js +88 -0
  27. package/dist/orm-request.d.ts +17 -1
  28. package/dist/orm-request.js +77 -19
  29. package/dist/postgres/column-rules.d.ts +33 -0
  30. package/dist/postgres/column-rules.js +34 -0
  31. package/dist/postgres/connection.d.ts +1 -0
  32. package/dist/postgres/connection.js +8 -6
  33. package/dist/postgres/postgres-db.d.ts +36 -2
  34. package/dist/postgres/postgres-db.js +244 -45
  35. package/dist/postgres/query-builder.d.ts +1 -0
  36. package/dist/postgres/query-builder.js +17 -0
  37. package/dist/record.js +7 -5
  38. package/dist/relationships.js +1 -1
  39. package/dist/serializer.js +38 -2
  40. package/dist/store.d.ts +13 -1
  41. package/dist/store.js +65 -6
  42. package/dist/timescale/query-builder.d.ts +1 -1
  43. package/dist/timescale/query-builder.js +1 -2
  44. package/dist/types/orm-types.d.ts +11 -0
  45. package/package.json +19 -7
  46. package/src/column-conversion.ts +53 -0
  47. package/src/commands.ts +43 -0
  48. package/src/dynamodb/connection.ts +50 -0
  49. package/src/dynamodb/dynamodb-db.ts +811 -0
  50. package/src/dynamodb/operation-builder.ts +202 -0
  51. package/src/dynamodb/type-map.ts +54 -0
  52. package/src/index.ts +4 -2
  53. package/src/main.ts +30 -1
  54. package/src/manage-record.ts +112 -33
  55. package/src/mysql/connection.ts +3 -0
  56. package/src/mysql/mysql-db.ts +264 -46
  57. package/src/mysql/query-builder.ts +21 -0
  58. package/src/normalize-record-id.ts +88 -0
  59. package/src/orm-request.ts +109 -18
  60. package/src/postgres/column-rules.ts +37 -0
  61. package/src/postgres/connection.ts +10 -6
  62. package/src/postgres/postgres-db.ts +277 -49
  63. package/src/postgres/query-builder.ts +21 -0
  64. package/src/record.ts +8 -5
  65. package/src/relationships.ts +1 -1
  66. package/src/serializer.ts +39 -2
  67. package/src/store.ts +68 -6
  68. package/src/timescale/query-builder.ts +1 -2
  69. package/src/types/orm-types.ts +12 -0
  70. package/src/types/stonyx.d.ts +7 -1
  71. package/config/environment.ts +0 -91
@@ -1,12 +1,111 @@
1
- // project configuration, override-able by listed environment variables
2
- const {
3
- DEBUG,
4
- NODE_ENV,
5
- } = process.env;
6
-
7
- const environment = NODE_ENV ?? 'development';
8
-
9
- export default {
10
- environment,
11
- debug: DEBUG ?? environment === 'development',
12
- }
1
+ const {
2
+ ORM_ACCESS_PATH,
3
+ ORM_MODEL_PATH,
4
+ ORM_REST_ROUTE,
5
+ ORM_SERIALIZER_PATH,
6
+ ORM_TRANSFORM_PATH,
7
+ ORM_VIEW_PATH,
8
+ ORM_USE_REST_SERVER,
9
+ DB_AUTO_SAVE,
10
+ DB_FILE,
11
+ DB_MODE,
12
+ DB_DIRECTORY,
13
+ DB_SCHEMA_PATH,
14
+ DB_SAVE_INTERVAL,
15
+ MYSQL_HOST,
16
+ MYSQL_PORT,
17
+ MYSQL_USER,
18
+ MYSQL_PASSWORD,
19
+ MYSQL_DATABASE,
20
+ MYSQL_CONNECTION_LIMIT,
21
+ MYSQL_MIGRATIONS_DIR,
22
+ MYSQL_WRITE_BATCH_SIZE,
23
+ MYSQL_WRITE_QUEUE_LIMIT,
24
+ PG_HOST,
25
+ PG_PORT,
26
+ PG_USER,
27
+ PG_PASSWORD,
28
+ PG_DATABASE,
29
+ PG_CONNECTION_LIMIT,
30
+ PG_MIGRATIONS_DIR,
31
+ PG_WRITE_BATCH_SIZE,
32
+ PG_WRITE_QUEUE_LIMIT,
33
+ TIMESCALE_HOST,
34
+ TIMESCALE_PORT,
35
+ TIMESCALE_USER,
36
+ TIMESCALE_PASSWORD,
37
+ TIMESCALE_DATABASE,
38
+ TIMESCALE_CONNECTION_LIMIT,
39
+ TIMESCALE_MIGRATIONS_DIR,
40
+ TIMESCALE_WRITE_BATCH_SIZE,
41
+ TIMESCALE_WRITE_QUEUE_LIMIT,
42
+ DYNAMODB_REGION,
43
+ DYNAMODB_ENDPOINT,
44
+ DYNAMODB_TABLE_PREFIX,
45
+ } = process.env;
46
+
47
+ export default {
48
+ logColor: 'white',
49
+ logMethod: 'db',
50
+
51
+ db: {
52
+ autosave: DB_AUTO_SAVE ?? 'false', // 'true' (cron interval), 'false' (disabled), 'onUpdate' (save after each write op)
53
+ file: DB_FILE ?? 'db.json',
54
+ mode: DB_MODE ?? 'file', // 'file' (single db.json) or 'directory' (one file per collection)
55
+ directory: DB_DIRECTORY ?? 'db', // directory name for collection files when mode is 'directory'
56
+ saveInterval: DB_SAVE_INTERVAL ?? 60 * 60, // 1 hour
57
+ schema: DB_SCHEMA_PATH ?? './config/db-schema.js'
58
+ },
59
+ paths: {
60
+ access: ORM_ACCESS_PATH ?? './access', // Optional for restServer access hooks
61
+ model: ORM_MODEL_PATH ?? './models',
62
+ serializer: ORM_SERIALIZER_PATH ?? './serializers',
63
+ transform: ORM_TRANSFORM_PATH ?? './transforms',
64
+ view: ORM_VIEW_PATH ?? './views'
65
+ },
66
+ mysql: MYSQL_HOST ? {
67
+ host: MYSQL_HOST ?? 'localhost',
68
+ port: parseInt(MYSQL_PORT ?? '3306'),
69
+ user: MYSQL_USER ?? 'root',
70
+ password: MYSQL_PASSWORD ?? '',
71
+ database: MYSQL_DATABASE ?? 'stonyx',
72
+ connectionLimit: parseInt(MYSQL_CONNECTION_LIMIT ?? '10'),
73
+ migrationsDir: MYSQL_MIGRATIONS_DIR ?? 'migrations',
74
+ migrationsTable: '__migrations',
75
+ writeBatchSize: parseInt(MYSQL_WRITE_BATCH_SIZE ?? '500'),
76
+ writeQueueLimit: parseInt(MYSQL_WRITE_QUEUE_LIMIT ?? '10000'),
77
+ } : undefined,
78
+ postgres: PG_HOST ? {
79
+ host: PG_HOST ?? 'localhost',
80
+ port: parseInt(PG_PORT ?? '5432'),
81
+ user: PG_USER ?? 'postgres',
82
+ password: PG_PASSWORD ?? '',
83
+ database: PG_DATABASE ?? 'stonyx',
84
+ connectionLimit: parseInt(PG_CONNECTION_LIMIT ?? '10'),
85
+ migrationsDir: PG_MIGRATIONS_DIR ?? 'migrations',
86
+ migrationsTable: '__migrations',
87
+ writeBatchSize: parseInt(PG_WRITE_BATCH_SIZE ?? '500'),
88
+ writeQueueLimit: parseInt(PG_WRITE_QUEUE_LIMIT ?? '10000'),
89
+ } : undefined,
90
+ timescale: TIMESCALE_HOST ? {
91
+ host: TIMESCALE_HOST ?? 'localhost',
92
+ port: parseInt(TIMESCALE_PORT ?? '5432'),
93
+ user: TIMESCALE_USER ?? 'postgres',
94
+ password: TIMESCALE_PASSWORD ?? '',
95
+ database: TIMESCALE_DATABASE ?? 'stonyx',
96
+ connectionLimit: parseInt(TIMESCALE_CONNECTION_LIMIT ?? '10'),
97
+ migrationsDir: TIMESCALE_MIGRATIONS_DIR ?? 'migrations',
98
+ migrationsTable: '__migrations',
99
+ writeBatchSize: parseInt(TIMESCALE_WRITE_BATCH_SIZE ?? '500'),
100
+ writeQueueLimit: parseInt(TIMESCALE_WRITE_QUEUE_LIMIT ?? '10000'),
101
+ } : undefined,
102
+ dynamodb: DYNAMODB_REGION ? {
103
+ region: DYNAMODB_REGION,
104
+ endpoint: DYNAMODB_ENDPOINT || undefined,
105
+ tablePrefix: DYNAMODB_TABLE_PREFIX || '',
106
+ } : undefined,
107
+ restServer: {
108
+ enabled: ORM_USE_REST_SERVER ?? 'true', // Whether to load restServer for automatic route setup or
109
+ route: ORM_REST_ROUTE ?? '/',
110
+ }
111
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shared write-path column conversion (#292).
3
+ *
4
+ * This module owns the *iteration* only — the single place where a write
5
+ * payload is walked column-by-column and each value is handed to the driver's
6
+ * own conversion rule. The rules themselves are genuinely disjoint per driver
7
+ * (Postgres stringifies for JSONB; DynamoDB's DocumentClient marshals objects
8
+ * and arrays natively and must NOT be given stringified values) and therefore
9
+ * deliberately live with their drivers rather than being merged here.
10
+ *
11
+ * The duplicated create/update iteration was the drift vector that produced
12
+ * #292: `_recordToRow` converted, `_persistUpdate` did not. Routing both
13
+ * through `convertRow` removes the opportunity for the two to diverge again.
14
+ */
15
+ /**
16
+ * A driver-supplied per-column conversion rule.
17
+ *
18
+ * @param value the raw value about to be bound into the query
19
+ * @param columnType the column's declared type from `schema.columns`, or
20
+ * `undefined` when the column is not an attribute column
21
+ * (e.g. `updated_at`, FK columns and `id`, which are added
22
+ * outside the attribute loop). Rules MUST be a no-op for
23
+ * `undefined`.
24
+ */
25
+ export type ColumnRule = (value: unknown, columnType: string | undefined) => unknown;
26
+ /**
27
+ * Applies `rule` to every column of a complete write payload.
28
+ *
29
+ * Call this on the *finished* payload immediately before handing it to the
30
+ * query builder — not inside a payload-building loop. Columns appended after
31
+ * such a loop (FKs, `updated_at`) would otherwise bypass conversion, which is
32
+ * the same class of defect #292 reports.
33
+ */
34
+ export declare function convertRow(row: Record<string, unknown>, columns: Record<string, string>, rule: ColumnRule): Record<string, unknown>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shared write-path column conversion (#292).
3
+ *
4
+ * This module owns the *iteration* only — the single place where a write
5
+ * payload is walked column-by-column and each value is handed to the driver's
6
+ * own conversion rule. The rules themselves are genuinely disjoint per driver
7
+ * (Postgres stringifies for JSONB; DynamoDB's DocumentClient marshals objects
8
+ * and arrays natively and must NOT be given stringified values) and therefore
9
+ * deliberately live with their drivers rather than being merged here.
10
+ *
11
+ * The duplicated create/update iteration was the drift vector that produced
12
+ * #292: `_recordToRow` converted, `_persistUpdate` did not. Routing both
13
+ * through `convertRow` removes the opportunity for the two to diverge again.
14
+ */
15
+ /**
16
+ * Applies `rule` to every column of a complete write payload.
17
+ *
18
+ * Call this on the *finished* payload immediately before handing it to the
19
+ * query builder — not inside a payload-building loop. Columns appended after
20
+ * such a loop (FKs, `updated_at`) would otherwise bypass conversion, which is
21
+ * the same class of defect #292 reports.
22
+ */
23
+ export function convertRow(row, columns, rule) {
24
+ const converted = {};
25
+ for (const [col, value] of Object.entries(row)) {
26
+ // `Object.hasOwn`, not `columns[col]`: a column named `toString` or
27
+ // `constructor` would otherwise reach the rule with a function as its
28
+ // "declared type". Inert for the shipped Postgres rule (strict equality
29
+ // against 'JSONB'), but `ColumnRule` is the extension point other drivers
30
+ // write against, and the obvious `if (columnType)` rule would invert.
31
+ converted[col] = rule(value, Object.hasOwn(columns, col) ? columns[col] : undefined);
32
+ }
33
+ return converted;
34
+ }
package/dist/commands.js CHANGED
@@ -20,6 +20,11 @@ const commands = {
20
20
  description: 'Generate a MySQL migration from current model schemas',
21
21
  bootstrap: true,
22
22
  run: async (args) => {
23
+ const config = (await import('stonyx/config')).default;
24
+ if (config.orm.dynamodb) {
25
+ console.log('DynamoDB does not use file-based migrations. Use db:sync to provision tables.');
26
+ return;
27
+ }
23
28
  const description = args?.join(' ') || 'migration';
24
29
  const { generateMigration } = await import('./mysql/migration-generator.js');
25
30
  const result = await generateMigration(description);
@@ -31,12 +36,33 @@ const commands = {
31
36
  }
32
37
  }
33
38
  },
39
+ 'db:sync': {
40
+ description: 'Provision DynamoDB tables and GSIs from current model schemas',
41
+ bootstrap: true,
42
+ run: async () => {
43
+ const config = (await import('stonyx/config')).default;
44
+ if (!config.orm.dynamodb) {
45
+ console.error('DynamoDB is not configured. Set DYNAMODB_REGION (and optionally DYNAMODB_ENDPOINT) to enable DynamoDB mode.');
46
+ process.exit(1);
47
+ }
48
+ const { default: DynamoDBDB } = await import('./dynamodb/dynamodb-db.js');
49
+ const db = new DynamoDBDB();
50
+ await db.init();
51
+ await db.startup();
52
+ await db.shutdown();
53
+ console.log('DynamoDB tables synced successfully.');
54
+ }
55
+ },
34
56
  'db:migrate': {
35
57
  description: 'Apply pending MySQL migrations',
36
58
  bootstrap: true,
37
59
  run: async () => {
38
60
  const config = (await import('stonyx/config')).default;
39
61
  const mysqlConfig = config.orm.mysql;
62
+ if (config.orm.dynamodb) {
63
+ console.log('DynamoDB does not use file-based migrations. Use db:sync to provision tables.');
64
+ return;
65
+ }
40
66
  if (!mysqlConfig) {
41
67
  console.error('MySQL is not configured. Set MYSQL_HOST to enable MySQL mode.');
42
68
  process.exit(1);
@@ -75,6 +101,10 @@ const commands = {
75
101
  bootstrap: true,
76
102
  run: async () => {
77
103
  const config = (await import('stonyx/config')).default;
104
+ if (config.orm.dynamodb) {
105
+ console.log('DynamoDB does not support migration rollback. Manage table changes via the AWS console or db:sync.');
106
+ return;
107
+ }
78
108
  const mysqlConfig = config.orm.mysql;
79
109
  if (!mysqlConfig) {
80
110
  console.error('MySQL is not configured. Set MYSQL_HOST to enable MySQL mode.');
@@ -113,6 +143,10 @@ const commands = {
113
143
  bootstrap: true,
114
144
  run: async () => {
115
145
  const config = (await import('stonyx/config')).default;
146
+ if (config.orm.dynamodb) {
147
+ console.log('DynamoDB does not use file-based migrations. Use db:sync to provision tables.');
148
+ return;
149
+ }
116
150
  const mysqlConfig = config.orm.mysql;
117
151
  if (!mysqlConfig) {
118
152
  console.error('MySQL is not configured. Set MYSQL_HOST to enable MySQL mode.');
@@ -0,0 +1,31 @@
1
+ /**
2
+ * DynamoDB connection factory.
3
+ *
4
+ * Dynamically imports @aws-sdk/client-dynamodb and @aws-sdk/lib-dynamodb
5
+ * so these are optional peerDependencies (matching the pg/mysql2 pattern).
6
+ */
7
+ export interface DynamoDBConfig {
8
+ region?: string;
9
+ endpoint?: string;
10
+ tablePrefix?: string;
11
+ [key: string]: unknown;
12
+ }
13
+ export type DocumentClient = {
14
+ send(command: unknown): Promise<unknown>;
15
+ };
16
+ export type DynamoDBClientConstructor = new (options: unknown) => {
17
+ config: unknown;
18
+ };
19
+ export type DocumentClientFromFn = {
20
+ from(client: unknown): DocumentClient;
21
+ };
22
+ /**
23
+ * Create a DynamoDBDocumentClient from the given config.
24
+ * Uses dynamic import so @aws-sdk/* are optional peer deps.
25
+ */
26
+ export declare function createDocumentClient(dbConfig: DynamoDBConfig): Promise<DocumentClient>;
27
+ /**
28
+ * Nullify the document client reference (DynamoDB connections are HTTP-based
29
+ * and stateless — no explicit pool close needed, but we clear the reference).
30
+ */
31
+ export declare function destroyDocumentClient(_client: DocumentClient | null): null;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * DynamoDB connection factory.
3
+ *
4
+ * Dynamically imports @aws-sdk/client-dynamodb and @aws-sdk/lib-dynamodb
5
+ * so these are optional peerDependencies (matching the pg/mysql2 pattern).
6
+ */
7
+ /**
8
+ * Create a DynamoDBDocumentClient from the given config.
9
+ * Uses dynamic import so @aws-sdk/* are optional peer deps.
10
+ */
11
+ export async function createDocumentClient(dbConfig) {
12
+ const { DynamoDBClient } = await import('@aws-sdk/client-dynamodb');
13
+ const { DynamoDBDocumentClient } = await import('@aws-sdk/lib-dynamodb');
14
+ const clientOptions = {};
15
+ if (dbConfig.region)
16
+ clientOptions.region = dbConfig.region;
17
+ if (dbConfig.endpoint)
18
+ clientOptions.endpoint = dbConfig.endpoint;
19
+ const rawClient = new DynamoDBClient(clientOptions);
20
+ return DynamoDBDocumentClient.from(rawClient);
21
+ }
22
+ /**
23
+ * Nullify the document client reference (DynamoDB connections are HTTP-based
24
+ * and stateless — no explicit pool close needed, but we clear the reference).
25
+ */
26
+ export function destroyDocumentClient(_client) {
27
+ return null;
28
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * DynamoDB driver implementing the SqlDb PAL contract.
3
+ *
4
+ * Drop-in replacement for PostgresDB / MysqlDB — zero ORM core changes.
5
+ * Selected via config.orm.dynamodb.
6
+ */
7
+ import { createDocumentClient, destroyDocumentClient } from './connection.js';
8
+ import type { DocumentClient, DynamoDBConfig } from './connection.js';
9
+ import { buildPutItem, buildGetItem, buildUpdateItem, buildDeleteItem, buildScan, buildQuery } from './operation-builder.js';
10
+ import { introspectModels, getTopologicalOrder } from '../postgres/schema-introspector.js';
11
+ import { getDynamoKeyType } from './type-map.js';
12
+ import { store } from '@stonyx/orm';
13
+ import { createRecord } from '../manage-record.js';
14
+ import { getPluralName } from '../plural-registry.js';
15
+ import config from 'stonyx/config';
16
+ import log from 'stonyx/log';
17
+ import type { OrmRecord } from '../types/orm-types.js';
18
+ /**
19
+ * Load the DynamoDB DocumentClient command constructors via dynamic import.
20
+ * Returns a frozen object so it can be cached in deps.
21
+ */
22
+ export declare function loadDocClientCommands(): Promise<{
23
+ PutCommand: new (params: unknown) => unknown;
24
+ GetCommand: new (params: unknown) => unknown;
25
+ UpdateCommand: new (params: unknown) => unknown;
26
+ DeleteCommand: new (params: unknown) => unknown;
27
+ ScanCommand: new (params: unknown) => unknown;
28
+ QueryCommand: new (params: unknown) => unknown;
29
+ }>;
30
+ export declare function loadTableCommands(): Promise<{
31
+ DynamoDBClient: new (opts: unknown) => {
32
+ send(cmd: unknown): Promise<unknown>;
33
+ };
34
+ DescribeTableCommand: new (params: unknown) => unknown;
35
+ CreateTableCommand: new (params: unknown) => unknown;
36
+ UpdateTableCommand: new (params: unknown) => unknown;
37
+ }>;
38
+ interface PersistContext {
39
+ record?: OrmRecord;
40
+ recordId?: unknown;
41
+ oldState?: Record<string, unknown>;
42
+ rawData?: Record<string, unknown>;
43
+ }
44
+ interface PersistResponse {
45
+ data?: {
46
+ id?: unknown;
47
+ };
48
+ }
49
+ /** Minimal Orm module shape needed at runtime — avoids circular import at top-level. */
50
+ interface OrmModule {
51
+ default: {
52
+ instance: {
53
+ getRecordClasses(name: string): {
54
+ modelClass: {
55
+ memory?: boolean;
56
+ };
57
+ };
58
+ isView?(name: string): boolean;
59
+ };
60
+ };
61
+ }
62
+ export interface DynamoDBDeps {
63
+ createDocumentClient: typeof createDocumentClient;
64
+ destroyDocumentClient: typeof destroyDocumentClient;
65
+ loadDocClientCommands: typeof loadDocClientCommands;
66
+ loadTableCommands: typeof loadTableCommands;
67
+ buildPutItem: typeof buildPutItem;
68
+ buildGetItem: typeof buildGetItem;
69
+ buildUpdateItem: typeof buildUpdateItem;
70
+ buildDeleteItem: typeof buildDeleteItem;
71
+ buildScan: typeof buildScan;
72
+ buildQuery: typeof buildQuery;
73
+ introspectModels: typeof introspectModels;
74
+ getTopologicalOrder: typeof getTopologicalOrder;
75
+ getDynamoKeyType: typeof getDynamoKeyType;
76
+ createRecord: typeof createRecord;
77
+ store: typeof store;
78
+ getPluralName: typeof getPluralName;
79
+ config: typeof config;
80
+ log: typeof log;
81
+ /** Injected for testing — import('@stonyx/orm') replacement */
82
+ _importOrm?: () => Promise<OrmModule>;
83
+ [key: string]: unknown;
84
+ }
85
+ export default class DynamoDBDB {
86
+ static instance: DynamoDBDB | undefined;
87
+ deps: DynamoDBDeps;
88
+ client: DocumentClient | null;
89
+ dbConfig: DynamoDBConfig;
90
+ /** GSI registry built during init from model introspection. */
91
+ private _gsiRegistry;
92
+ constructor(deps?: Partial<DynamoDBDeps>);
93
+ private requireClient;
94
+ private _resolveTableName;
95
+ /** Resolve Orm singleton — falls back to real import in production. */
96
+ private _getOrm;
97
+ init(): Promise<void>;
98
+ /**
99
+ * For each model, DescribeTable — CreateTable if missing (with GSIs, PAY_PER_REQUEST).
100
+ * For existing tables, check for missing GSIs and UpdateTable + poll for ACTIVE.
101
+ */
102
+ startup(): Promise<void>;
103
+ shutdown(): Promise<void>;
104
+ /**
105
+ * DynamoDB does NOT use write serialization (#156).
106
+ *
107
+ * Unlike MySQL/PostgreSQL, DynamoDB has no server-side foreign key
108
+ * constraints and no multi-row transactions in standard single-item
109
+ * operations (PutItem, UpdateItem, DeleteItem). Each operation is
110
+ * atomic at the item level and cannot deadlock against other items.
111
+ * Concurrent fire-and-forget writes therefore cannot produce the
112
+ * cross-row lock contention that causes InnoDB/PG deadlocks.
113
+ */
114
+ persist(operation: string, modelName: string, context: PersistContext, response: PersistResponse): Promise<void>;
115
+ findRecord(modelName: string, id: unknown): Promise<OrmRecord | undefined>;
116
+ findAll(modelName: string, conditions?: Record<string, unknown>): Promise<OrmRecord[]>;
117
+ loadMemoryRecords(): Promise<void>;
118
+ private _persistCreate;
119
+ private _persistUpdate;
120
+ private _persistDelete;
121
+ private _paginatedScan;
122
+ private _paginatedQuery;
123
+ /**
124
+ * Build the GSI registry from model introspection.
125
+ * Registry: modelName → attrName → gsiName
126
+ *
127
+ * FK columns (belonging to belongsTo relationships) get a GSI automatically.
128
+ */
129
+ private _buildGsiRegistry;
130
+ /**
131
+ * Find a GSI that can serve the given conditions.
132
+ */
133
+ private _findGsiMatch;
134
+ private _buildAttributeDefinitions;
135
+ private _buildGsiDefinitions;
136
+ private _waitForTableActive;
137
+ private _itemToRawData;
138
+ private _recordToItem;
139
+ private _evictIfNotMemory;
140
+ loadAllRecords(): Promise<void>;
141
+ }
142
+ export {};