@stonyx/orm 0.3.2-beta.8 → 0.3.2-beta.80

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.
@@ -0,0 +1,188 @@
1
+ /**
2
+ * DynamoDB operation parameter builders.
3
+ *
4
+ * Each function returns a plain-object "params" bag that can be passed
5
+ * directly to the corresponding DocumentClient command
6
+ * (PutCommand, GetCommand, UpdateCommand, DeleteCommand, ScanCommand, QueryCommand).
7
+ *
8
+ * All functions are pure — no SDK imports here; the caller wraps params
9
+ * in the appropriate Command class.
10
+ */
11
+
12
+ export interface PutItemParams {
13
+ TableName: string;
14
+ Item: Record<string, unknown>;
15
+ ConditionExpression?: string;
16
+ }
17
+
18
+ export interface GetItemParams {
19
+ TableName: string;
20
+ Key: Record<string, unknown>;
21
+ }
22
+
23
+ export interface UpdateItemParams {
24
+ TableName: string;
25
+ Key: Record<string, unknown>;
26
+ UpdateExpression: string;
27
+ ExpressionAttributeNames: Record<string, string>;
28
+ ExpressionAttributeValues: Record<string, unknown>;
29
+ ReturnValues: string;
30
+ }
31
+
32
+ export interface DeleteItemParams {
33
+ TableName: string;
34
+ Key: Record<string, unknown>;
35
+ }
36
+
37
+ export interface ScanParams {
38
+ TableName: string;
39
+ FilterExpression?: string;
40
+ ExpressionAttributeNames?: Record<string, string>;
41
+ ExpressionAttributeValues?: Record<string, unknown>;
42
+ ExclusiveStartKey?: Record<string, unknown>;
43
+ }
44
+
45
+ export interface QueryParams {
46
+ TableName: string;
47
+ IndexName: string;
48
+ KeyConditionExpression: string;
49
+ ExpressionAttributeNames: Record<string, string>;
50
+ ExpressionAttributeValues: Record<string, unknown>;
51
+ ExclusiveStartKey?: Record<string, unknown>;
52
+ }
53
+
54
+ /**
55
+ * PutItem — optionally with a condition expression.
56
+ *
57
+ * Pass conditionExpression = 'attribute_not_exists(id)' to enforce uniqueness.
58
+ */
59
+ export function buildPutItem(
60
+ tableName: string,
61
+ item: Record<string, unknown>,
62
+ conditionExpression?: string,
63
+ ): PutItemParams {
64
+ const params: PutItemParams = { TableName: tableName, Item: item };
65
+ if (conditionExpression) params.ConditionExpression = conditionExpression;
66
+ return params;
67
+ }
68
+
69
+ /**
70
+ * GetItem by primary key.
71
+ */
72
+ export function buildGetItem(
73
+ tableName: string,
74
+ key: Record<string, unknown>,
75
+ ): GetItemParams {
76
+ return { TableName: tableName, Key: key };
77
+ }
78
+
79
+ /**
80
+ * UpdateItem with a SET expression built from the `updates` object.
81
+ * Only the supplied attributes are updated (diff-based call site).
82
+ */
83
+ export function buildUpdateItem(
84
+ tableName: string,
85
+ key: Record<string, unknown>,
86
+ updates: Record<string, unknown>,
87
+ ): UpdateItemParams {
88
+ const names: Record<string, string> = {};
89
+ const values: Record<string, unknown> = {};
90
+ const setClauses: string[] = [];
91
+
92
+ for (const [attr, val] of Object.entries(updates)) {
93
+ const nameAlias = `#${attr}`;
94
+ const valAlias = `:${attr}`;
95
+ names[nameAlias] = attr;
96
+ values[valAlias] = val;
97
+ setClauses.push(`${nameAlias} = ${valAlias}`);
98
+ }
99
+
100
+ return {
101
+ TableName: tableName,
102
+ Key: key,
103
+ UpdateExpression: `SET ${setClauses.join(', ')}`,
104
+ ExpressionAttributeNames: names,
105
+ ExpressionAttributeValues: values,
106
+ ReturnValues: 'NONE',
107
+ };
108
+ }
109
+
110
+ /**
111
+ * DeleteItem by primary key.
112
+ */
113
+ export function buildDeleteItem(
114
+ tableName: string,
115
+ key: Record<string, unknown>,
116
+ ): DeleteItemParams {
117
+ return { TableName: tableName, Key: key };
118
+ }
119
+
120
+ /**
121
+ * ScanCommand params.
122
+ * If conditions are supplied they are rendered as a FilterExpression using AND.
123
+ */
124
+ export function buildScan(
125
+ tableName: string,
126
+ conditions?: Record<string, unknown>,
127
+ exclusiveStartKey?: Record<string, unknown>,
128
+ ): ScanParams {
129
+ const params: ScanParams = { TableName: tableName };
130
+
131
+ if (exclusiveStartKey) params.ExclusiveStartKey = exclusiveStartKey;
132
+
133
+ if (conditions && Object.keys(conditions).length > 0) {
134
+ const names: Record<string, string> = {};
135
+ const values: Record<string, unknown> = {};
136
+ const clauses: string[] = [];
137
+
138
+ for (const [attr, val] of Object.entries(conditions)) {
139
+ const nameAlias = `#${attr}`;
140
+ const valAlias = `:${attr}`;
141
+ names[nameAlias] = attr;
142
+ values[valAlias] = val;
143
+ clauses.push(`${nameAlias} = ${valAlias}`);
144
+ }
145
+
146
+ params.FilterExpression = clauses.join(' AND ');
147
+ params.ExpressionAttributeNames = names;
148
+ params.ExpressionAttributeValues = values;
149
+ }
150
+
151
+ return params;
152
+ }
153
+
154
+ /**
155
+ * QueryCommand params for a GSI.
156
+ * keyConditions must be in the form { attrName: value } and will be rendered
157
+ * as equality expressions joined by AND.
158
+ */
159
+ export function buildQuery(
160
+ tableName: string,
161
+ indexName: string,
162
+ keyConditions: Record<string, unknown>,
163
+ exclusiveStartKey?: Record<string, unknown>,
164
+ ): QueryParams {
165
+ const names: Record<string, string> = {};
166
+ const values: Record<string, unknown> = {};
167
+ const clauses: string[] = [];
168
+
169
+ for (const [attr, val] of Object.entries(keyConditions)) {
170
+ const nameAlias = `#${attr}`;
171
+ const valAlias = `:${attr}`;
172
+ names[nameAlias] = attr;
173
+ values[valAlias] = val;
174
+ clauses.push(`${nameAlias} = ${valAlias}`);
175
+ }
176
+
177
+ const params: QueryParams = {
178
+ TableName: tableName,
179
+ IndexName: indexName,
180
+ KeyConditionExpression: clauses.join(' AND '),
181
+ ExpressionAttributeNames: names,
182
+ ExpressionAttributeValues: values,
183
+ };
184
+
185
+ if (exclusiveStartKey) params.ExclusiveStartKey = exclusiveStartKey;
186
+
187
+ return params;
188
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Maps ORM attribute types to DynamoDB scalar attribute types.
3
+ * DynamoDB DocumentClient auto-marshalls JS objects, so most values
4
+ * are sent as their native JS types. This map is used by the
5
+ * schema-introspector and startup provisioner for table/GSI creation.
6
+ */
7
+
8
+ export type DynamoScalarType = 'S' | 'N' | 'BOOL';
9
+
10
+ /**
11
+ * DynamoDB attribute-type string for a given ORM attr type.
12
+ * - string → S
13
+ * - number / float → N (stored as Number; DocumentClient handles it)
14
+ * - boolean → BOOL
15
+ * - date → S (ISO-8601 string — enables range queries)
16
+ * - timestamp → N (milliseconds since epoch)
17
+ * - passthrough/trim/etc → S (safe default)
18
+ *
19
+ * For key schema declarations only `S` and `N` are valid; BOOL
20
+ * is legal for attributes but never for a PK/SK.
21
+ */
22
+ const typeMap: Record<string, DynamoScalarType> = {
23
+ string: 'S',
24
+ number: 'N',
25
+ float: 'N',
26
+ boolean: 'BOOL',
27
+ date: 'S',
28
+ timestamp: 'N',
29
+ passthrough: 'S',
30
+ trim: 'S',
31
+ uppercase: 'S',
32
+ ceil: 'N',
33
+ floor: 'N',
34
+ round: 'N',
35
+ };
36
+
37
+ /**
38
+ * Returns the DynamoDB attribute type for a given ORM type string.
39
+ * Defaults to 'S' for any unknown/custom type.
40
+ */
41
+ export function getDynamoType(attrType: string): DynamoScalarType {
42
+ return typeMap[attrType] ?? 'S';
43
+ }
44
+
45
+ /**
46
+ * Returns the DynamoDB key type ('S' | 'N') for use in KeySchema.
47
+ * BOOL cannot be a key attribute; anything that maps to BOOL falls back to 'S'.
48
+ */
49
+ export function getDynamoKeyType(attrType: string): 'S' | 'N' {
50
+ const t = getDynamoType(attrType);
51
+ return t === 'N' ? 'N' : 'S';
52
+ }
53
+
54
+ export default typeMap;
package/src/main.ts CHANGED
@@ -90,6 +90,11 @@ export default class Orm {
90
90
  }
91
91
 
92
92
  async init(): Promise<void> {
93
+ // Self-register so log.db works even when @stonyx/orm is in the
94
+ // consumer's `dependencies` (stonyx loader only merges devDependencies).
95
+ const { logColor = 'white', logMethod = 'db' } = config.orm;
96
+ log.defineType(logMethod, logColor);
97
+
93
98
  const { paths, restServer } = config.orm;
94
99
 
95
100
  const promises: Promise<unknown>[] = ['Model', 'Serializer', 'Transform'].map(type => {
@@ -159,6 +164,11 @@ export default class Orm {
159
164
  this.sqlDb = new MysqlDB() as SqlDb;
160
165
  this.db = this.sqlDb;
161
166
  promises.push(this.sqlDb.init());
167
+ } else if (config.orm.dynamodb) {
168
+ const { default: DynamoDBDB } = await import('./dynamodb/dynamodb-db.js');
169
+ this.sqlDb = new DynamoDBDB() as SqlDb;
170
+ this.db = this.sqlDb;
171
+ promises.push(this.sqlDb.init());
162
172
  } else if (this.options.dbType !== 'none') {
163
173
  const db = new DB();
164
174
  this.db = db;
@@ -79,6 +79,28 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
79
79
  pendingHasMany.splice(0);
80
80
  }
81
81
 
82
+ // FK-based inverse hasMany wiring — when a child record is created with a
83
+ // foreign-key field (e.g. `owner: 'owner-1'` on an animal), find any parent
84
+ // whose hasMany registry targets this model and push the child into the
85
+ // parent's shared array. This covers edge cases where the child is created
86
+ // in a separate async frame without a belongsTo handler firing.
87
+ const hasManyReg = getHasManyRegistry();
88
+ if (hasManyReg) {
89
+ for (const [parentModelName, targetMap] of hasManyReg) {
90
+ const childArrayMap = targetMap.get(modelName);
91
+ if (!childArrayMap) continue;
92
+
93
+ // Check if rawData contains a FK field matching the parent model name
94
+ const fkValue = rawData[parentModelName];
95
+ if (fkValue === undefined || fkValue === null) continue;
96
+
97
+ const parentArray = childArrayMap.get(fkValue);
98
+ if (parentArray && !parentArray.includes(record)) {
99
+ parentArray.push(record);
100
+ }
101
+ }
102
+ }
103
+
82
104
  // Fulfill pending belongsTo relationships
83
105
  const pendingBelongsToQueue = getPendingBelongsToRegistry();
84
106
  const pendingBelongsToRaw = pendingBelongsToQueue.get(modelName)?.get(record.id);
@@ -86,7 +108,7 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
86
108
 
87
109
  if (pendingBelongsTo) {
88
110
  const belongsToReg = getBelongsToRegistry();
89
- const hasManyReg = getHasManyRegistry();
111
+ const pendingHasManyReg = getHasManyRegistry();
90
112
 
91
113
  for (const { sourceRecord, sourceModelName, relationshipKey, relationshipId } of pendingBelongsTo) {
92
114
  // Update the belongsTo relationship on the source record
@@ -103,7 +125,7 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
103
125
  }
104
126
 
105
127
  // Wire inverse hasMany if it exists
106
- const inverseHasMany = hasManyReg.get(modelName)?.get(sourceModelName)?.get(record.id);
128
+ const inverseHasMany = pendingHasManyReg.get(modelName)?.get(sourceModelName)?.get(record.id);
107
129
 
108
130
  if (inverseHasMany && !inverseHasMany.includes(sourceRecord)) {
109
131
  inverseHasMany.push(sourceRecord);
@@ -117,15 +139,25 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
117
139
  // Auto-persist to SQL — skip for DB loads (isDbRecord) and relationship resolution (_relationshipKey)
118
140
  const shouldPersist = orm?.sqlDb && !options.isDbRecord && !userOptions._relationshipKey && !options._skipAutoPersist;
119
141
  if (shouldPersist) {
142
+ // Capture ID before persist — SQL adapters re-key pending IDs to real DB IDs,
143
+ // but relationship registries were keyed with this original ID
144
+ const registryId = record.id;
120
145
  const response = { data: { id: record.id } };
121
- orm!.sqlDb!.persist('create', modelName, { rawData }, response).catch((err: unknown) => {
122
- orm!.emitPersistError({
123
- operation: 'create',
124
- modelName,
125
- recordId: record.id,
126
- error: err instanceof Error ? err : new Error(String(err)),
146
+ orm!.sqlDb!.persist('create', modelName, { rawData }, response)
147
+ .catch((err: unknown) => {
148
+ orm!.emitPersistError({
149
+ operation: 'create',
150
+ modelName,
151
+ recordId: record.id,
152
+ error: err instanceof Error ? err : new Error(String(err)),
153
+ });
154
+ })
155
+ .finally(() => {
156
+ // Evict non-memory records after persist to prevent unbounded heap growth (stonyx#81)
157
+ if (store._memoryResolver && !store._memoryResolver(modelName)) {
158
+ store.evictRecord(modelName, record.id, registryId);
159
+ }
127
160
  });
128
- });
129
161
  }
130
162
 
131
163
  return record;
@@ -8,6 +8,7 @@ interface PgConfig {
8
8
  password: string;
9
9
  database: string;
10
10
  connectionLimit: number;
11
+ [key: string]: unknown;
11
12
  }
12
13
 
13
14
  let pool: PgPool | null = null;
@@ -20,15 +21,18 @@ export async function getPool(pgConfig: PgConfig, extensions: string[] = ['vecto
20
21
 
21
22
  const { default: pg } = await import('pg');
22
23
 
24
+ const { host, port, user, password, database, connectionLimit, migrationsDir, migrationsTable, ...poolOpts } = pgConfig;
25
+
23
26
  pool = new pg.Pool({
24
- host: pgConfig.host,
25
- port: pgConfig.port,
26
- user: pgConfig.user,
27
- password: pgConfig.password,
28
- database: pgConfig.database,
29
- max: pgConfig.connectionLimit,
27
+ host,
28
+ port,
29
+ user,
30
+ password,
31
+ database,
32
+ max: connectionLimit,
30
33
  idleTimeoutMillis: 30000,
31
34
  connectionTimeoutMillis: 10000,
35
+ ...poolOpts,
32
36
  });
33
37
 
34
38
  // Enable requested PostgreSQL extensions
@@ -51,4 +51,4 @@ export function getPendingBelongsToRegistry(): PendingBelongsToMap {
51
51
  return relationships.get('pendingBelongsTo') as PendingBelongsToMap;
52
52
  }
53
53
 
54
- export const TYPES: string[] = ['global', 'hasMany', 'belongsTo', 'pending'];
54
+ export const TYPES: string[] = ['global', 'hasMany', 'belongsTo', 'pending', 'pendingBelongsTo'];
package/src/serializer.ts CHANGED
@@ -94,8 +94,33 @@ export default class Serializer {
94
94
  const handlerOptions = { ...options, _relationshipKey: key };
95
95
  const childRecord = handler(record, data, handlerOptions);
96
96
 
97
- rec[key] = childRecord;
98
- relatedRecords[key] = childRecord;
97
+ // hasMany relationships use a getter so format()/toJSON() always read
98
+ // the live registry array instead of a stale snapshot captured at
99
+ // serialization time. This is critical when child records are created
100
+ // in a later async frame — the belongsTo inverse wiring pushes into
101
+ // the shared registry array, and the getter ensures the parent sees it.
102
+ const isHasMany = (handler as { __relationshipType?: string }).__relationshipType === 'hasMany';
103
+
104
+ if (isHasMany) {
105
+ // `childRecord` IS the shared registry array — define a getter that
106
+ // always dereferences through the same array reference.
107
+ const registryArray = childRecord as unknown[];
108
+ Object.defineProperty(rec, key, {
109
+ enumerable: true,
110
+ configurable: true,
111
+ get: () => registryArray,
112
+ set(v: unknown) { relatedRecords[key] = v; }
113
+ });
114
+ Object.defineProperty(relatedRecords, key, {
115
+ enumerable: true,
116
+ configurable: true,
117
+ get: () => registryArray,
118
+ set(v: unknown) { Object.defineProperty(relatedRecords, key, { value: v, writable: true, enumerable: true, configurable: true }); }
119
+ });
120
+ } else {
121
+ rec[key] = childRecord;
122
+ relatedRecords[key] = childRecord;
123
+ }
99
124
 
100
125
  continue;
101
126
  }
package/src/store.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import Orm, { relationships } from '@stonyx/orm';
2
- import { TYPES, getHasManyRegistry, getBelongsToRegistry, getPendingRegistry } from './relationships.js';
2
+ import { TYPES, getHasManyRegistry, getBelongsToRegistry, getPendingRegistry, getPendingBelongsToRegistry } from './relationships.js';
3
3
  import ViewResolver from './view-resolver.js';
4
4
 
5
5
  interface UnloadOptions {
@@ -193,6 +193,44 @@ export default class Store {
193
193
  this.unloadAllRecords(key);
194
194
  }
195
195
 
196
+ /**
197
+ * Evict a record from the store with full relationship registry cleanup,
198
+ * WITHOUT calling record.clean(). This preserves the caller's reference
199
+ * to the returned record (used by memory:false post-persist eviction).
200
+ *
201
+ * @param registryId - The ID used when the record's relationships were
202
+ * registered. For SQL models with pending IDs, this is the original
203
+ * negative pending ID (before the adapter re-keyed to the real DB ID).
204
+ */
205
+ evictRecord(modelName: string, id: unknown, registryId?: unknown): void {
206
+ const modelStore = this.data.get(modelName);
207
+ if (!modelStore) return;
208
+
209
+ if (typeof id !== 'string' && typeof id !== 'number') return;
210
+ const raw = modelStore.get(id);
211
+ if (!raw || !isStoreRecord(raw)) return;
212
+
213
+ const visited = new Set([`${modelName}:${id}`]);
214
+
215
+ // Remove from hasMany arrays and nullify belongsTo references using current ID
216
+ // (the adapter updates record.id, so value-based matches need the current ID)
217
+ this._removeFromHasManyArrays(modelName, id, visited);
218
+ this._nullifyBelongsToReferences(modelName, id, visited);
219
+
220
+ // Clean up relationship registry entries using the registry key
221
+ // (belongsTo/hasMany registries were keyed by the ID at registration time,
222
+ // which may differ from the current ID if SQL persist re-keyed the record)
223
+ const cleanupId = registryId ?? id;
224
+ this._cleanupRelationshipRegistries(modelName, cleanupId);
225
+
226
+ // If registryId differs from id, also clean with current id as safety net
227
+ if (registryId !== undefined && registryId !== id) {
228
+ this._cleanupRelationshipRegistries(modelName, id);
229
+ }
230
+
231
+ modelStore.delete(id);
232
+ }
233
+
196
234
  unloadRecord(model: string, id: unknown, options: UnloadOptions = {}): void {
197
235
  const modelStore = this.data.get(model);
198
236
 
@@ -308,6 +346,30 @@ export default class Store {
308
346
 
309
347
  const pendingMap = getPendingRegistry().get(modelName);
310
348
  if (pendingMap) pendingMap.delete(recordId);
349
+
350
+ // Clean pendingBelongsTo entries in both directions
351
+ const pendingBelongsToMap = getPendingBelongsToRegistry();
352
+ if (pendingBelongsToMap) {
353
+ // Direction 1: evicted record was the TARGET others were waiting for
354
+ const targetEntries = pendingBelongsToMap.get(modelName);
355
+ if (targetEntries) targetEntries.delete(recordId);
356
+
357
+ // Direction 2: evicted record was the SOURCE with unresolved forward-references
358
+ for (const [, targetIdMap] of pendingBelongsToMap) {
359
+ for (const [targetId, entries] of targetIdMap) {
360
+ if (!Array.isArray(entries)) continue;
361
+ const filtered = entries.filter((e: unknown) => {
362
+ const entry = e as { sourceModelName?: string; relationshipId?: unknown };
363
+ return !(entry.sourceModelName === modelName && entry.relationshipId === recordId);
364
+ });
365
+ if (filtered.length === 0) {
366
+ targetIdMap.delete(targetId);
367
+ } else if (filtered.length < entries.length) {
368
+ targetIdMap.set(targetId, filtered);
369
+ }
370
+ }
371
+ }
372
+ }
311
373
  }
312
374
 
313
375
  /**
@@ -48,6 +48,12 @@ export interface OrmRestServerConfig {
48
48
  metaRoute: boolean;
49
49
  }
50
50
 
51
+ export interface OrmDynamoDBConfig {
52
+ region?: string;
53
+ endpoint?: string;
54
+ [key: string]: unknown;
55
+ }
56
+
51
57
  export interface OrmSection {
52
58
  db: OrmDbConfig;
53
59
  paths: OrmPaths;
@@ -55,6 +61,9 @@ export interface OrmSection {
55
61
  mysql?: OrmMysqlConfig;
56
62
  postgres?: OrmPostgresConfig;
57
63
  timescale?: OrmPostgresConfig;
64
+ dynamodb?: OrmDynamoDBConfig;
65
+ logColor?: string;
66
+ logMethod?: string;
58
67
  [key: string]: unknown;
59
68
  }
60
69
 
@@ -5,7 +5,13 @@ declare module 'stonyx/config' {
5
5
  }
6
6
 
7
7
  declare module 'stonyx/log' {
8
- const log: Record<string, ((...args: unknown[]) => void) | undefined>;
8
+ interface Log {
9
+ db(message: string): void;
10
+ error(message: string, ...args: unknown[]): void;
11
+ defineType(type: string, setting: string, options?: Record<string, unknown> | null): void;
12
+ [key: string]: ((...args: unknown[]) => void) | undefined;
13
+ }
14
+ const log: Log;
9
15
  export default log;
10
16
  }
11
17
 
@@ -1,91 +0,0 @@
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
- PG_HOST,
23
- PG_PORT,
24
- PG_USER,
25
- PG_PASSWORD,
26
- PG_DATABASE,
27
- PG_CONNECTION_LIMIT,
28
- PG_MIGRATIONS_DIR,
29
- TIMESCALE_HOST,
30
- TIMESCALE_PORT,
31
- TIMESCALE_USER,
32
- TIMESCALE_PASSWORD,
33
- TIMESCALE_DATABASE,
34
- TIMESCALE_CONNECTION_LIMIT,
35
- TIMESCALE_MIGRATIONS_DIR,
36
- } = process.env;
37
-
38
- export default {
39
- logColor: 'white',
40
- logMethod: 'db',
41
-
42
- db: {
43
- autosave: DB_AUTO_SAVE ?? 'false', // 'true' (cron interval), 'false' (disabled), 'onUpdate' (save after each write op)
44
- file: DB_FILE ?? 'db.json',
45
- mode: DB_MODE ?? 'file', // 'file' (single db.json) or 'directory' (one file per collection)
46
- directory: DB_DIRECTORY ?? 'db', // directory name for collection files when mode is 'directory'
47
- saveInterval: DB_SAVE_INTERVAL ?? 60 * 60, // 1 hour
48
- schema: DB_SCHEMA_PATH ?? './config/db-schema.js'
49
- },
50
- paths: {
51
- access: ORM_ACCESS_PATH ?? './access', // Optional for restServer access hooks
52
- model: ORM_MODEL_PATH ?? './models',
53
- serializer: ORM_SERIALIZER_PATH ?? './serializers',
54
- transform: ORM_TRANSFORM_PATH ?? './transforms',
55
- view: ORM_VIEW_PATH ?? './views'
56
- },
57
- mysql: MYSQL_HOST ? {
58
- host: MYSQL_HOST ?? 'localhost',
59
- port: parseInt(MYSQL_PORT ?? '3306'),
60
- user: MYSQL_USER ?? 'root',
61
- password: MYSQL_PASSWORD ?? '',
62
- database: MYSQL_DATABASE ?? 'stonyx',
63
- connectionLimit: parseInt(MYSQL_CONNECTION_LIMIT ?? '10'),
64
- migrationsDir: MYSQL_MIGRATIONS_DIR ?? 'migrations',
65
- migrationsTable: '__migrations',
66
- } : undefined,
67
- postgres: PG_HOST ? {
68
- host: PG_HOST ?? 'localhost',
69
- port: parseInt(PG_PORT ?? '5432'),
70
- user: PG_USER ?? 'postgres',
71
- password: PG_PASSWORD ?? '',
72
- database: PG_DATABASE ?? 'stonyx',
73
- connectionLimit: parseInt(PG_CONNECTION_LIMIT ?? '10'),
74
- migrationsDir: PG_MIGRATIONS_DIR ?? 'migrations',
75
- migrationsTable: '__migrations',
76
- } : undefined,
77
- timescale: TIMESCALE_HOST ? {
78
- host: TIMESCALE_HOST ?? 'localhost',
79
- port: parseInt(TIMESCALE_PORT ?? '5432'),
80
- user: TIMESCALE_USER ?? 'postgres',
81
- password: TIMESCALE_PASSWORD ?? '',
82
- database: TIMESCALE_DATABASE ?? 'stonyx',
83
- connectionLimit: parseInt(TIMESCALE_CONNECTION_LIMIT ?? '10'),
84
- migrationsDir: TIMESCALE_MIGRATIONS_DIR ?? 'migrations',
85
- migrationsTable: '__migrations',
86
- } : undefined,
87
- restServer: {
88
- enabled: ORM_USE_REST_SERVER ?? 'true', // Whether to load restServer for automatic route setup or
89
- route: ORM_REST_ROUTE ?? '/',
90
- }
91
- }