metal-orm 1.0.33 → 1.0.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.33",
3
+ "version": "1.0.34",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -14,11 +14,6 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "import": "./dist/index.js",
16
16
  "require": "./dist/index.cjs"
17
- },
18
- "./decorators": {
19
- "types": "./dist/decorators/index.d.ts",
20
- "import": "./dist/decorators/index.js",
21
- "require": "./dist/decorators/index.cjs"
22
17
  }
23
18
  },
24
19
  "files": [
@@ -438,7 +438,7 @@ const renderEntityFile = (schema, options) => {
438
438
  ];
439
439
  const decoratorImports = decoratorOrder.filter(name => decoratorSet.has(name));
440
440
  if (decoratorImports.length) {
441
- imports.push(`import { ${decoratorImports.join(', ')} } from 'metal-orm/decorators';`);
441
+ imports.push(`import { ${decoratorImports.join(', ')} } from 'metal-orm';`);
442
442
  }
443
443
 
444
444
  const ormTypes = [];
package/src/index.ts CHANGED
@@ -38,6 +38,7 @@ export * from './orm/hydration-context.js';
38
38
  export * from './orm/domain-event-bus.js';
39
39
  export * from './orm/runtime-types.js';
40
40
  export * from './orm/query-logger.js';
41
+ export * from './decorators/index.js';
41
42
 
42
43
  // NEW: execution abstraction + helpers
43
44
  export * from './core/execution/db-executor.js';
package/src/orm/entity.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TableDef } from '../schema/table.js';
2
- import { Entity, RelationMap, HasManyCollection, HasOneReference, BelongsToReference, ManyToManyCollection } from '../schema/types.js';
2
+ import { EntityInstance, RelationMap, HasManyCollection, HasOneReference, BelongsToReference, ManyToManyCollection } from '../schema/types.js';
3
3
  import { EntityContext } from './entity-context.js';
4
4
  import { ENTITY_META, EntityMeta, getEntityMeta } from './entity-meta.js';
5
5
  import { DefaultHasManyCollection } from './relations/has-many.js';
@@ -49,7 +49,7 @@ export const createEntityProxy = <
49
49
  table: TTable,
50
50
  row: Record<string, any>,
51
51
  lazyRelations: TLazy[] = [] as TLazy[]
52
- ): Entity<TTable> => {
52
+ ): EntityInstance<TTable> => {
53
53
  const target: Record<string, any> = { ...row };
54
54
  const meta: EntityMeta<TTable> = {
55
55
  ctx,
@@ -66,7 +66,7 @@ export const createEntityProxy = <
66
66
  writable: false
67
67
  });
68
68
 
69
- let proxy: Entity<TTable>;
69
+ let proxy: EntityInstance<TTable>;
70
70
  const handler: ProxyHandler<any> = {
71
71
  get(targetObj, prop, receiver) {
72
72
  if (prop === ENTITY_META) {
@@ -99,7 +99,7 @@ export const createEntityProxy = <
99
99
  }
100
100
  };
101
101
 
102
- proxy = new Proxy(target, handler) as Entity<TTable>;
102
+ proxy = new Proxy(target, handler) as EntityInstance<TTable>;
103
103
  populateHydrationCache(proxy, row, meta);
104
104
  return proxy;
105
105
  };
@@ -109,7 +109,7 @@ export const createEntityFromRow = <TTable extends TableDef>(
109
109
  table: TTable,
110
110
  row: Record<string, any>,
111
111
  lazyRelations: (keyof RelationMap<TTable>)[] = []
112
- ): Entity<TTable> => {
112
+ ): EntityInstance<TTable> => {
113
113
  const pkName = findPrimaryKey(table);
114
114
  const pkValue = row[pkName];
115
115
  if (pkValue !== undefined && pkValue !== null) {
@@ -1,5 +1,5 @@
1
1
  import { TableDef } from '../schema/table.js';
2
- import { Entity } from '../schema/types.js';
2
+ import { EntityInstance } from '../schema/types.js';
3
3
  import { hydrateRows } from './hydration.js';
4
4
  import { OrmSession } from './orm-session.ts';
5
5
  import { SelectQueryBuilder } from '../query-builder/select.js';
@@ -28,7 +28,7 @@ const flattenResults = (results: { columns: string[]; values: unknown[][] }[]):
28
28
  const executeWithEntityContext = async <TTable extends TableDef>(
29
29
  entityCtx: EntityContext,
30
30
  qb: SelectQueryBuilder<any, TTable>
31
- ): Promise<Entity<TTable>[]> => {
31
+ ): Promise<EntityInstance<TTable>[]> => {
32
32
  const ast = qb.getAST();
33
33
  const compiled = entityCtx.dialect.compileSelect(ast);
34
34
  const executed = await entityCtx.executor.executeSql(compiled.sql, compiled.params);
@@ -45,7 +45,7 @@ const executeWithEntityContext = async <TTable extends TableDef>(
45
45
  export async function executeHydrated<TTable extends TableDef>(
46
46
  session: OrmSession,
47
47
  qb: SelectQueryBuilder<any, TTable>
48
- ): Promise<Entity<TTable>[]> {
48
+ ): Promise<EntityInstance<TTable>[]> {
49
49
  return executeWithEntityContext(session, qb);
50
50
  }
51
51
 
@@ -53,7 +53,7 @@ export async function executeHydratedWithContexts<TTable extends TableDef>(
53
53
  _execCtx: ExecutionContext,
54
54
  hydCtx: HydrationContext,
55
55
  qb: SelectQueryBuilder<any, TTable>
56
- ): Promise<Entity<TTable>[]> {
56
+ ): Promise<EntityInstance<TTable>[]> {
57
57
  const entityCtx = hydCtx.entityContext;
58
58
  if (!entityCtx) {
59
59
  throw new Error('Hydration context is missing an EntityContext');
@@ -1,139 +1,139 @@
1
- import { Dialect } from '../core/dialect/abstract.js';
2
- import { eq } from '../core/ast/expression.js';
3
- import type { DbExecutor } from '../core/execution/db-executor.js';
1
+ import { Dialect } from '../core/dialect/abstract.js';
2
+ import { eq } from '../core/ast/expression.js';
3
+ import type { DbExecutor } from '../core/execution/db-executor.js';
4
4
  import { SelectQueryBuilder } from '../query-builder/select.js';
5
5
  import { findPrimaryKey } from '../query-builder/hydration-planner.js';
6
6
  import type { ColumnDef } from '../schema/column.js';
7
7
  import type { TableDef } from '../schema/table.js';
8
- import { Entity } from '../schema/types.js';
9
- import { RelationDef } from '../schema/relation.js';
10
-
11
- import { selectFromEntity, getTableDefFromEntity } from '../decorators/bootstrap.js';
12
- import type { EntityConstructor } from './entity-metadata.js';
13
- import { Orm } from './orm.js';
14
- import { IdentityMap } from './identity-map.js';
15
- import { UnitOfWork } from './unit-of-work.js';
16
- import { DomainEventBus, DomainEventHandler, InitialHandlers } from './domain-event-bus.js';
17
- import { RelationChangeProcessor } from './relation-change-processor.js';
18
- import { createQueryLoggingExecutor, QueryLogger } from './query-logger.js';
19
- import { ExecutionContext } from './execution-context.js';
20
- import type { HydrationContext } from './hydration-context.js';
21
- import type { EntityContext } from './entity-context.js';
22
- import {
23
- DomainEvent,
24
- OrmDomainEvent,
25
- RelationChange,
26
- RelationChangeEntry,
27
- RelationKey,
28
- TrackedEntity
29
- } from './runtime-types.js';
30
- import { executeHydrated } from './execute.js';
31
- import { runInTransaction } from './transaction-runner.js';
32
-
33
- export interface OrmInterceptor {
34
- beforeFlush?(ctx: EntityContext): Promise<void> | void;
35
- afterFlush?(ctx: EntityContext): Promise<void> | void;
36
- }
37
-
38
- export interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
39
- orm: Orm<E>;
40
- executor: DbExecutor;
41
- queryLogger?: QueryLogger;
42
- interceptors?: OrmInterceptor[];
43
- domainEventHandlers?: InitialHandlers<E, OrmSession<E>>;
44
- }
45
-
46
- export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements EntityContext {
47
- readonly orm: Orm<E>;
48
- readonly executor: DbExecutor;
49
- readonly identityMap: IdentityMap;
50
- readonly unitOfWork: UnitOfWork;
51
- readonly domainEvents: DomainEventBus<E, OrmSession<E>>;
52
- readonly relationChanges: RelationChangeProcessor;
53
-
54
- private readonly interceptors: OrmInterceptor[];
55
-
56
- constructor(opts: OrmSessionOptions<E>) {
57
- this.orm = opts.orm;
58
- this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
59
- this.interceptors = [...(opts.interceptors ?? [])];
60
-
61
- this.identityMap = new IdentityMap();
62
- this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
63
- this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
64
- this.domainEvents = new DomainEventBus<E, OrmSession<E>>(opts.domainEventHandlers);
65
- }
66
-
67
- get dialect(): Dialect {
68
- return this.orm.dialect;
69
- }
70
-
71
- get identityBuckets(): Map<string, Map<string, TrackedEntity>> {
72
- return this.unitOfWork.identityBuckets;
73
- }
74
-
75
- get tracked(): TrackedEntity[] {
76
- return this.unitOfWork.getTracked();
77
- }
78
-
79
- getEntity(table: TableDef, pk: any): any | undefined {
80
- return this.unitOfWork.getEntity(table, pk);
81
- }
82
-
83
- setEntity(table: TableDef, pk: any, entity: any): void {
84
- this.unitOfWork.setEntity(table, pk, entity);
85
- }
86
-
87
- trackNew(table: TableDef, entity: any, pk?: any): void {
88
- this.unitOfWork.trackNew(table, entity, pk);
89
- }
90
-
91
- trackManaged(table: TableDef, pk: any, entity: any): void {
92
- this.unitOfWork.trackManaged(table, pk, entity);
93
- }
94
-
95
- markDirty(entity: any): void {
96
- this.unitOfWork.markDirty(entity);
97
- }
98
-
99
- markRemoved(entity: any): void {
100
- this.unitOfWork.markRemoved(entity);
101
- }
102
-
103
- registerRelationChange = (
104
- root: any,
105
- relationKey: RelationKey,
106
- rootTable: TableDef,
107
- relationName: string,
108
- relation: RelationDef,
109
- change: RelationChange<any>
110
- ): void => {
111
- this.relationChanges.registerChange(
112
- buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
113
- );
114
- };
115
-
116
- getEntitiesForTable(table: TableDef): TrackedEntity[] {
117
- return this.unitOfWork.getEntitiesForTable(table);
118
- }
119
-
120
- registerInterceptor(interceptor: OrmInterceptor): void {
121
- this.interceptors.push(interceptor);
122
- }
123
-
124
- registerDomainEventHandler<TType extends E['type']>(
125
- type: TType,
126
- handler: DomainEventHandler<Extract<E, { type: TType }>, OrmSession<E>>
127
- ): void {
128
- this.domainEvents.on(type, handler);
129
- }
130
-
131
- async find<TTable extends TableDef>(entityClass: EntityConstructor, id: any): Promise<Entity<TTable> | null> {
132
- const table = getTableDefFromEntity(entityClass);
133
- if (!table) {
134
- throw new Error('Entity metadata has not been bootstrapped');
135
- }
136
- const primaryKey = findPrimaryKey(table);
8
+ import { EntityInstance } from '../schema/types.js';
9
+ import { RelationDef } from '../schema/relation.js';
10
+
11
+ import { selectFromEntity, getTableDefFromEntity } from '../decorators/bootstrap.js';
12
+ import type { EntityConstructor } from './entity-metadata.js';
13
+ import { Orm } from './orm.js';
14
+ import { IdentityMap } from './identity-map.js';
15
+ import { UnitOfWork } from './unit-of-work.js';
16
+ import { DomainEventBus, DomainEventHandler, InitialHandlers } from './domain-event-bus.js';
17
+ import { RelationChangeProcessor } from './relation-change-processor.js';
18
+ import { createQueryLoggingExecutor, QueryLogger } from './query-logger.js';
19
+ import { ExecutionContext } from './execution-context.js';
20
+ import type { HydrationContext } from './hydration-context.js';
21
+ import type { EntityContext } from './entity-context.js';
22
+ import {
23
+ DomainEvent,
24
+ OrmDomainEvent,
25
+ RelationChange,
26
+ RelationChangeEntry,
27
+ RelationKey,
28
+ TrackedEntity
29
+ } from './runtime-types.js';
30
+ import { executeHydrated } from './execute.js';
31
+ import { runInTransaction } from './transaction-runner.js';
32
+
33
+ export interface OrmInterceptor {
34
+ beforeFlush?(ctx: EntityContext): Promise<void> | void;
35
+ afterFlush?(ctx: EntityContext): Promise<void> | void;
36
+ }
37
+
38
+ export interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
39
+ orm: Orm<E>;
40
+ executor: DbExecutor;
41
+ queryLogger?: QueryLogger;
42
+ interceptors?: OrmInterceptor[];
43
+ domainEventHandlers?: InitialHandlers<E, OrmSession<E>>;
44
+ }
45
+
46
+ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements EntityContext {
47
+ readonly orm: Orm<E>;
48
+ readonly executor: DbExecutor;
49
+ readonly identityMap: IdentityMap;
50
+ readonly unitOfWork: UnitOfWork;
51
+ readonly domainEvents: DomainEventBus<E, OrmSession<E>>;
52
+ readonly relationChanges: RelationChangeProcessor;
53
+
54
+ private readonly interceptors: OrmInterceptor[];
55
+
56
+ constructor(opts: OrmSessionOptions<E>) {
57
+ this.orm = opts.orm;
58
+ this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
59
+ this.interceptors = [...(opts.interceptors ?? [])];
60
+
61
+ this.identityMap = new IdentityMap();
62
+ this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
63
+ this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
64
+ this.domainEvents = new DomainEventBus<E, OrmSession<E>>(opts.domainEventHandlers);
65
+ }
66
+
67
+ get dialect(): Dialect {
68
+ return this.orm.dialect;
69
+ }
70
+
71
+ get identityBuckets(): Map<string, Map<string, TrackedEntity>> {
72
+ return this.unitOfWork.identityBuckets;
73
+ }
74
+
75
+ get tracked(): TrackedEntity[] {
76
+ return this.unitOfWork.getTracked();
77
+ }
78
+
79
+ getEntity(table: TableDef, pk: any): any | undefined {
80
+ return this.unitOfWork.getEntity(table, pk);
81
+ }
82
+
83
+ setEntity(table: TableDef, pk: any, entity: any): void {
84
+ this.unitOfWork.setEntity(table, pk, entity);
85
+ }
86
+
87
+ trackNew(table: TableDef, entity: any, pk?: any): void {
88
+ this.unitOfWork.trackNew(table, entity, pk);
89
+ }
90
+
91
+ trackManaged(table: TableDef, pk: any, entity: any): void {
92
+ this.unitOfWork.trackManaged(table, pk, entity);
93
+ }
94
+
95
+ markDirty(entity: any): void {
96
+ this.unitOfWork.markDirty(entity);
97
+ }
98
+
99
+ markRemoved(entity: any): void {
100
+ this.unitOfWork.markRemoved(entity);
101
+ }
102
+
103
+ registerRelationChange = (
104
+ root: any,
105
+ relationKey: RelationKey,
106
+ rootTable: TableDef,
107
+ relationName: string,
108
+ relation: RelationDef,
109
+ change: RelationChange<any>
110
+ ): void => {
111
+ this.relationChanges.registerChange(
112
+ buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
113
+ );
114
+ };
115
+
116
+ getEntitiesForTable(table: TableDef): TrackedEntity[] {
117
+ return this.unitOfWork.getEntitiesForTable(table);
118
+ }
119
+
120
+ registerInterceptor(interceptor: OrmInterceptor): void {
121
+ this.interceptors.push(interceptor);
122
+ }
123
+
124
+ registerDomainEventHandler<TType extends E['type']>(
125
+ type: TType,
126
+ handler: DomainEventHandler<Extract<E, { type: TType }>, OrmSession<E>>
127
+ ): void {
128
+ this.domainEvents.on(type, handler);
129
+ }
130
+
131
+ async find<TTable extends TableDef>(entityClass: EntityConstructor, id: any): Promise<EntityInstance<TTable> | null> {
132
+ const table = getTableDefFromEntity(entityClass);
133
+ if (!table) {
134
+ throw new Error('Entity metadata has not been bootstrapped');
135
+ }
136
+ const primaryKey = findPrimaryKey(table);
137
137
  const column = table.columns[primaryKey];
138
138
  if (!column) {
139
139
  throw new Error('Entity table does not expose a primary key');
@@ -146,100 +146,100 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
146
146
  .select(columnSelections)
147
147
  .where(eq(column, id))
148
148
  .limit(1);
149
- const rows = await executeHydrated(this, qb);
150
- return rows[0] ?? null;
151
- }
152
-
153
- async findOne<TTable extends TableDef>(qb: SelectQueryBuilder<any, TTable>): Promise<Entity<TTable> | null> {
154
- const limited = qb.limit(1);
155
- const rows = await executeHydrated(this, limited);
156
- return rows[0] ?? null;
157
- }
158
-
159
- async findMany<TTable extends TableDef>(qb: SelectQueryBuilder<any, TTable>): Promise<Entity<TTable>[]> {
160
- return executeHydrated(this, qb);
161
- }
162
-
163
- async persist(entity: object): Promise<void> {
164
- if (this.unitOfWork.findTracked(entity)) {
165
- return;
166
- }
167
- const table = getTableDefFromEntity((entity as any).constructor as EntityConstructor);
168
- if (!table) {
169
- throw new Error('Entity metadata has not been bootstrapped');
170
- }
171
- const primaryKey = findPrimaryKey(table);
172
- const pkValue = (entity as Record<string, any>)[primaryKey];
173
- if (pkValue !== undefined && pkValue !== null) {
174
- this.trackManaged(table, pkValue, entity);
175
- } else {
176
- this.trackNew(table, entity);
177
- }
178
- }
179
-
180
- async remove(entity: object): Promise<void> {
181
- this.markRemoved(entity);
182
- }
183
-
184
- async flush(): Promise<void> {
185
- await this.unitOfWork.flush();
186
- }
187
-
188
- async commit(): Promise<void> {
189
- await runInTransaction(this.executor, async () => {
190
- for (const interceptor of this.interceptors) {
191
- await interceptor.beforeFlush?.(this);
192
- }
193
-
194
- await this.unitOfWork.flush();
195
- await this.relationChanges.process();
196
- await this.unitOfWork.flush();
197
-
198
- for (const interceptor of this.interceptors) {
199
- await interceptor.afterFlush?.(this);
200
- }
201
- });
202
-
203
- await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
204
- }
205
-
206
- async rollback(): Promise<void> {
207
- await this.executor.rollbackTransaction?.();
208
- this.unitOfWork.reset();
209
- this.relationChanges.reset();
210
- }
211
-
212
- getExecutionContext(): ExecutionContext {
213
- return {
214
- dialect: this.orm.dialect,
215
- executor: this.executor,
216
- interceptors: this.orm.interceptors
217
- };
218
- }
219
-
220
- getHydrationContext(): HydrationContext<E> {
221
- return {
222
- identityMap: this.identityMap,
223
- unitOfWork: this.unitOfWork,
224
- domainEvents: this.domainEvents,
225
- relationChanges: this.relationChanges,
226
- entityContext: this
227
- };
228
- }
229
- }
230
-
231
- const buildRelationChangeEntry = (
232
- root: any,
233
- relationKey: RelationKey,
234
- rootTable: TableDef,
235
- relationName: string,
236
- relation: RelationDef,
237
- change: RelationChange<any>
238
- ): RelationChangeEntry => ({
239
- root,
240
- relationKey,
241
- rootTable,
242
- relationName,
243
- relation,
244
- change
245
- });
149
+ const rows = await executeHydrated(this, qb);
150
+ return rows[0] ?? null;
151
+ }
152
+
153
+ async findOne<TTable extends TableDef>(qb: SelectQueryBuilder<any, TTable>): Promise<EntityInstance<TTable> | null> {
154
+ const limited = qb.limit(1);
155
+ const rows = await executeHydrated(this, limited);
156
+ return rows[0] ?? null;
157
+ }
158
+
159
+ async findMany<TTable extends TableDef>(qb: SelectQueryBuilder<any, TTable>): Promise<EntityInstance<TTable>[]> {
160
+ return executeHydrated(this, qb);
161
+ }
162
+
163
+ async persist(entity: object): Promise<void> {
164
+ if (this.unitOfWork.findTracked(entity)) {
165
+ return;
166
+ }
167
+ const table = getTableDefFromEntity((entity as any).constructor as EntityConstructor);
168
+ if (!table) {
169
+ throw new Error('Entity metadata has not been bootstrapped');
170
+ }
171
+ const primaryKey = findPrimaryKey(table);
172
+ const pkValue = (entity as Record<string, any>)[primaryKey];
173
+ if (pkValue !== undefined && pkValue !== null) {
174
+ this.trackManaged(table, pkValue, entity);
175
+ } else {
176
+ this.trackNew(table, entity);
177
+ }
178
+ }
179
+
180
+ async remove(entity: object): Promise<void> {
181
+ this.markRemoved(entity);
182
+ }
183
+
184
+ async flush(): Promise<void> {
185
+ await this.unitOfWork.flush();
186
+ }
187
+
188
+ async commit(): Promise<void> {
189
+ await runInTransaction(this.executor, async () => {
190
+ for (const interceptor of this.interceptors) {
191
+ await interceptor.beforeFlush?.(this);
192
+ }
193
+
194
+ await this.unitOfWork.flush();
195
+ await this.relationChanges.process();
196
+ await this.unitOfWork.flush();
197
+
198
+ for (const interceptor of this.interceptors) {
199
+ await interceptor.afterFlush?.(this);
200
+ }
201
+ });
202
+
203
+ await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
204
+ }
205
+
206
+ async rollback(): Promise<void> {
207
+ await this.executor.rollbackTransaction?.();
208
+ this.unitOfWork.reset();
209
+ this.relationChanges.reset();
210
+ }
211
+
212
+ getExecutionContext(): ExecutionContext {
213
+ return {
214
+ dialect: this.orm.dialect,
215
+ executor: this.executor,
216
+ interceptors: this.orm.interceptors
217
+ };
218
+ }
219
+
220
+ getHydrationContext(): HydrationContext<E> {
221
+ return {
222
+ identityMap: this.identityMap,
223
+ unitOfWork: this.unitOfWork,
224
+ domainEvents: this.domainEvents,
225
+ relationChanges: this.relationChanges,
226
+ entityContext: this
227
+ };
228
+ }
229
+ }
230
+
231
+ const buildRelationChangeEntry = (
232
+ root: any,
233
+ relationKey: RelationKey,
234
+ rootTable: TableDef,
235
+ relationName: string,
236
+ relation: RelationDef,
237
+ change: RelationChange<any>
238
+ ): RelationChangeEntry => ({
239
+ root,
240
+ relationKey,
241
+ rootTable,
242
+ relationName,
243
+ relation,
244
+ change
245
+ });