serverless-simple-middleware 0.0.63 → 0.0.65

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 (52) hide show
  1. package/.prettierignore +2 -2
  2. package/README.md +3 -3
  3. package/dist/aws/config.js +11 -12
  4. package/dist/aws/index.d.ts +1 -1
  5. package/dist/aws/index.js +1 -1
  6. package/dist/aws/simple.d.ts +1 -1
  7. package/dist/aws/simple.js +534 -786
  8. package/dist/internal/AwsError.js +10 -28
  9. package/dist/middleware/aws.js +42 -125
  10. package/dist/middleware/base.d.ts +3 -3
  11. package/dist/middleware/base.js +84 -104
  12. package/dist/middleware/build.js +101 -244
  13. package/dist/middleware/database/connectionProxy.d.ts +19 -0
  14. package/dist/middleware/database/connectionProxy.js +136 -0
  15. package/dist/middleware/database/index.d.ts +2 -0
  16. package/dist/middleware/database/index.js +7 -0
  17. package/dist/middleware/database/sqlClient.d.ts +8 -0
  18. package/dist/middleware/database/sqlClient.js +65 -0
  19. package/dist/middleware/index.d.ts +4 -3
  20. package/dist/middleware/index.js +8 -7
  21. package/dist/middleware/logger.js +13 -69
  22. package/dist/middleware/mysql.d.ts +11 -23
  23. package/dist/middleware/mysql.js +25 -288
  24. package/dist/middleware/trace.d.ts +1 -1
  25. package/dist/middleware/trace.js +144 -216
  26. package/dist/utils/logger.js +29 -30
  27. package/dist/utils/misc.js +6 -6
  28. package/jest.config.js +7 -7
  29. package/package.json +71 -69
  30. package/src/aws/config.ts +46 -46
  31. package/src/aws/define.ts +10 -10
  32. package/src/aws/index.ts +3 -3
  33. package/src/aws/simple.ts +686 -686
  34. package/src/index.ts +3 -3
  35. package/src/internal/AwsError.ts +13 -13
  36. package/src/internal/s3.ts +75 -75
  37. package/src/middleware/aws.ts +78 -78
  38. package/src/middleware/base.ts +189 -185
  39. package/src/middleware/build.ts +173 -173
  40. package/src/middleware/database/connectionProxy.ts +174 -0
  41. package/src/middleware/database/index.ts +14 -0
  42. package/src/middleware/database/sqlClient.ts +95 -0
  43. package/src/middleware/index.ts +21 -20
  44. package/src/middleware/logger.ts +28 -28
  45. package/src/middleware/mysql.ts +52 -214
  46. package/src/middleware/trace.ts +265 -265
  47. package/src/utils/index.ts +2 -2
  48. package/src/utils/logger.ts +94 -94
  49. package/src/utils/misc.ts +20 -20
  50. package/tsconfig.json +15 -15
  51. package/tslint.json +12 -12
  52. package/.idea/workspace.xml +0 -99
@@ -1,173 +1,173 @@
1
- import { getLogger } from '../utils/logger';
2
-
3
- import { stringifyError } from '../utils';
4
- import {
5
- Handler,
6
- HandlerAuxBase,
7
- HandlerPluginBase,
8
- HandlerRequest,
9
- HandlerResponse,
10
- } from './base';
11
-
12
- const logger = getLogger(__filename);
13
-
14
- type Delegator = (okResponsible: boolean) => Promise<any>;
15
-
16
- class HandlerMiddleware<A extends HandlerAuxBase> {
17
- public auxPromise: Promise<A>;
18
- public plugins: Array<HandlerPluginBase<any>>;
19
-
20
- constructor(plugins: Array<HandlerPluginBase<any>>) {
21
- this.plugins = plugins;
22
- this.auxPromise = this.createAuxPromise();
23
- }
24
-
25
- private createAuxPromise = (): Promise<A> => {
26
- return !this.plugins || this.plugins.length === 0
27
- ? Promise.resolve({} as A) // tslint:disable-line
28
- : Promise.all(
29
- this.plugins.map((plugin) => {
30
- const maybePromise = plugin.create();
31
- return maybePromise instanceof Promise
32
- ? maybePromise
33
- : Promise.resolve(maybePromise);
34
- }),
35
- ).then(
36
- (auxes) =>
37
- auxes.reduce((all, each) => ({ ...all, ...each }), {}) as A,
38
- );
39
- };
40
- }
41
-
42
- class HandlerProxy<A extends HandlerAuxBase> {
43
- private request: HandlerRequest;
44
- private response: HandlerResponse;
45
- private aux: A;
46
-
47
- public constructor(event: any, context: any, callback: any) {
48
- logger.stupid(`event`, event);
49
- this.request = new HandlerRequest(event, context);
50
- this.response = new HandlerResponse(callback);
51
- this.aux = {} as A; // tslint:disable-line
52
- }
53
-
54
- public call = async (
55
- middleware: HandlerMiddleware<A>,
56
- handler: Handler<A>,
57
- ) => {
58
- try {
59
- this.aux = await middleware.auxPromise;
60
- } catch (error) {
61
- logger.error(
62
- `Error while initializing plugins' aux: ${stringifyError(error)}`,
63
- );
64
- this.response.fail(
65
- error instanceof Error ? { error: error.message } : error,
66
- );
67
- return [error];
68
- }
69
-
70
- const actualHandler = [this.generateDelegator(handler)];
71
- const beginHandlers = middleware.plugins.map((plugin) =>
72
- this.generateDelegator(plugin.begin),
73
- );
74
- const endHandlers = middleware.plugins.map((plugin) =>
75
- this.generateDelegator(plugin.end),
76
- );
77
- const errorHandlers = middleware.plugins.map((plugin) =>
78
- this.generateDelegator(plugin.error),
79
- );
80
-
81
- const iterate = async (
82
- handlers: Delegator[],
83
- okResponsible: boolean = false,
84
- ) =>
85
- Promise.all(
86
- handlers.map((each) =>
87
- this.safeCall(each, okResponsible, errorHandlers),
88
- ),
89
- );
90
-
91
- const results = [
92
- ...(await iterate(beginHandlers)),
93
- ...(await iterate(actualHandler, true)),
94
- ...(await iterate(endHandlers)),
95
- ].filter((x) => x);
96
- // In test phase, throws any exception if there was.
97
- if (process.env.NODE_ENV === 'test') {
98
- for (const each of results) {
99
- if (each instanceof Error) {
100
- logger.error(`Error occurred: ${stringifyError(each)}`);
101
- throw each;
102
- }
103
- }
104
- }
105
- results.forEach((result) =>
106
- logger.silly(`middleware result : ${JSON.stringify(result)}`),
107
- );
108
- };
109
-
110
- private safeCall = async (
111
- delegator: Delegator,
112
- okResponsible: boolean,
113
- errorHandlers: Delegator[],
114
- ) => {
115
- try {
116
- const result = await delegator(okResponsible);
117
- return result;
118
- } catch (error) {
119
- const handled = await this.handleError(error, errorHandlers);
120
- return handled;
121
- }
122
- };
123
-
124
- private generateDelegator =
125
- (handler: Handler<A>): Delegator =>
126
- async (okResponsible: boolean) => {
127
- const maybePromise = handler({
128
- request: this.request,
129
- response: this.response,
130
- aux: this.aux,
131
- });
132
- const result =
133
- maybePromise instanceof Promise ? await maybePromise : maybePromise;
134
- logger.stupid(`result`, result);
135
- if (!this.response.completed && okResponsible) {
136
- this.response.ok(result);
137
- }
138
- return result;
139
- };
140
-
141
- private handleError = async (error: Error, errorHandlers?: Delegator[]) => {
142
- logger.error(error);
143
- this.request.lastError = error;
144
-
145
- if (errorHandlers) {
146
- for (const handler of errorHandlers) {
147
- try {
148
- await handler(false);
149
- } catch (ignorable) {
150
- logger.error(ignorable);
151
- }
152
- }
153
- }
154
- if (!this.response.completed) {
155
- this.response.fail(
156
- error instanceof Error ? { error: error.message } : error,
157
- );
158
- }
159
- return error;
160
- };
161
- }
162
-
163
- // It will break type safety because there is no relation between Aux and Plugin.
164
- const build = <Aux extends HandlerAuxBase>(
165
- plugins: Array<HandlerPluginBase<any>>,
166
- ) => {
167
- const middleware = new HandlerMiddleware<Aux>(plugins);
168
- return (handler: Handler<Aux>) =>
169
- (event: any, context: any, callback: any) => {
170
- new HandlerProxy<Aux>(event, context, callback).call(middleware, handler);
171
- };
172
- };
173
- export default build;
1
+ import { getLogger } from '../utils/logger';
2
+
3
+ import { stringifyError } from '../utils';
4
+ import {
5
+ Handler,
6
+ HandlerAuxBase,
7
+ HandlerPluginBase,
8
+ HandlerRequest,
9
+ HandlerResponse,
10
+ } from './base';
11
+
12
+ const logger = getLogger(__filename);
13
+
14
+ type Delegator = (okResponsible: boolean) => Promise<any>;
15
+
16
+ class HandlerMiddleware<A extends HandlerAuxBase> {
17
+ public auxPromise: Promise<A>;
18
+ public plugins: Array<HandlerPluginBase<any>>;
19
+
20
+ constructor(plugins: Array<HandlerPluginBase<any>>) {
21
+ this.plugins = plugins;
22
+ this.auxPromise = this.createAuxPromise();
23
+ }
24
+
25
+ private createAuxPromise = (): Promise<A> => {
26
+ return !this.plugins || this.plugins.length === 0
27
+ ? Promise.resolve({} as A) // tslint:disable-line
28
+ : Promise.all(
29
+ this.plugins.map((plugin) => {
30
+ const maybePromise = plugin.create();
31
+ return maybePromise instanceof Promise
32
+ ? maybePromise
33
+ : Promise.resolve(maybePromise);
34
+ }),
35
+ ).then(
36
+ (auxes) =>
37
+ auxes.reduce((all, each) => ({ ...all, ...each }), {}) as A,
38
+ );
39
+ };
40
+ }
41
+
42
+ class HandlerProxy<A extends HandlerAuxBase> {
43
+ private request: HandlerRequest;
44
+ private response: HandlerResponse;
45
+ private aux: A;
46
+
47
+ public constructor(event: any, context: any, callback: any) {
48
+ logger.stupid(`event`, event);
49
+ this.request = new HandlerRequest(event, context);
50
+ this.response = new HandlerResponse(callback);
51
+ this.aux = {} as A; // tslint:disable-line
52
+ }
53
+
54
+ public call = async (
55
+ middleware: HandlerMiddleware<A>,
56
+ handler: Handler<A>,
57
+ ) => {
58
+ try {
59
+ this.aux = await middleware.auxPromise;
60
+ } catch (error) {
61
+ logger.error(
62
+ `Error while initializing plugins' aux: ${stringifyError(error)}`,
63
+ );
64
+ this.response.fail(
65
+ error instanceof Error ? { error: error.message } : error,
66
+ );
67
+ return [error];
68
+ }
69
+
70
+ const actualHandler = [this.generateDelegator(handler)];
71
+ const beginHandlers = middleware.plugins.map((plugin) =>
72
+ this.generateDelegator(plugin.begin),
73
+ );
74
+ const endHandlers = middleware.plugins.map((plugin) =>
75
+ this.generateDelegator(plugin.end),
76
+ );
77
+ const errorHandlers = middleware.plugins.map((plugin) =>
78
+ this.generateDelegator(plugin.error),
79
+ );
80
+
81
+ const iterate = async (
82
+ handlers: Delegator[],
83
+ okResponsible: boolean = false,
84
+ ) =>
85
+ Promise.all(
86
+ handlers.map((each) =>
87
+ this.safeCall(each, okResponsible, errorHandlers),
88
+ ),
89
+ );
90
+
91
+ const results = [
92
+ ...(await iterate(beginHandlers)),
93
+ ...(await iterate(actualHandler, true)),
94
+ ...(await iterate(endHandlers)),
95
+ ].filter((x) => x);
96
+ // In test phase, throws any exception if there was.
97
+ if (process.env.NODE_ENV === 'test') {
98
+ for (const each of results) {
99
+ if (each instanceof Error) {
100
+ logger.error(`Error occurred: ${stringifyError(each)}`);
101
+ throw each;
102
+ }
103
+ }
104
+ }
105
+ results.forEach((result) =>
106
+ logger.silly(`middleware result : ${JSON.stringify(result)}`),
107
+ );
108
+ };
109
+
110
+ private safeCall = async (
111
+ delegator: Delegator,
112
+ okResponsible: boolean,
113
+ errorHandlers: Delegator[],
114
+ ) => {
115
+ try {
116
+ const result = await delegator(okResponsible);
117
+ return result;
118
+ } catch (error) {
119
+ const handled = await this.handleError(error, errorHandlers);
120
+ return handled;
121
+ }
122
+ };
123
+
124
+ private generateDelegator =
125
+ (handler: Handler<A>): Delegator =>
126
+ async (okResponsible: boolean) => {
127
+ const maybePromise = handler({
128
+ request: this.request,
129
+ response: this.response,
130
+ aux: this.aux,
131
+ });
132
+ const result =
133
+ maybePromise instanceof Promise ? await maybePromise : maybePromise;
134
+ logger.stupid(`result`, result);
135
+ if (!this.response.completed && okResponsible) {
136
+ this.response.ok(result);
137
+ }
138
+ return result;
139
+ };
140
+
141
+ private handleError = async (error: Error, errorHandlers?: Delegator[]) => {
142
+ logger.error(error);
143
+ this.request.lastError = error;
144
+
145
+ if (errorHandlers) {
146
+ for (const handler of errorHandlers) {
147
+ try {
148
+ await handler(false);
149
+ } catch (ignorable) {
150
+ logger.error(ignorable);
151
+ }
152
+ }
153
+ }
154
+ if (!this.response.completed) {
155
+ this.response.fail(
156
+ error instanceof Error ? { error: error.message } : error,
157
+ );
158
+ }
159
+ return error;
160
+ };
161
+ }
162
+
163
+ // It will break type safety because there is no relation between Aux and Plugin.
164
+ const build = <Aux extends HandlerAuxBase>(
165
+ plugins: Array<HandlerPluginBase<any>>,
166
+ ) => {
167
+ const middleware = new HandlerMiddleware<Aux>(plugins);
168
+ return (handler: Handler<Aux>) =>
169
+ (event: any, context: any, callback: any) => {
170
+ new HandlerProxy<Aux>(event, context, callback).call(middleware, handler);
171
+ };
172
+ };
173
+ export default build;
@@ -0,0 +1,174 @@
1
+ import { createConnection, type Connection, type MysqlError } from 'mysql';
2
+ import { getLogger } from '../../utils';
3
+ import { MySQLPluginOptions } from '../mysql';
4
+
5
+ const logger = getLogger(__filename);
6
+
7
+ export class ConnectionProxy {
8
+ private pluginConfig: MySQLPluginOptions;
9
+ private connection?: Connection;
10
+
11
+ private initialized: boolean;
12
+ private dbName?: string;
13
+
14
+ public constructor(config: MySQLPluginOptions) {
15
+ this.pluginConfig = config;
16
+ if (config.schema && config.schema.database) {
17
+ this.dbName = config.config.database;
18
+ config.config.database = undefined;
19
+ }
20
+ }
21
+
22
+ public query = <T>(sql: string, params?: any[]) =>
23
+ new Promise<T | undefined>(async (resolve, reject) => {
24
+ const connection = this.prepareConnection();
25
+ await this.tryToInitializeSchema(false);
26
+
27
+ if (process.env.NODE_ENV !== 'test') {
28
+ logger.silly(`Execute query[${sql}] with params[${params}]`);
29
+ }
30
+ connection.query(sql, params, (err: MysqlError, result?: T) => {
31
+ if (err) {
32
+ logger.error(`error occurred in database query=${sql}, error=${err}`);
33
+ reject(err);
34
+ } else {
35
+ resolve(result);
36
+ if (process.env.NODE_ENV !== 'test') {
37
+ logger.silly(`DB result is ${JSON.stringify(result)}`);
38
+ }
39
+ }
40
+ });
41
+ });
42
+
43
+ public fetch = <T>(sql: string, params?: any[]) =>
44
+ this.query<T[]>(sql, params).then((res) => res || []);
45
+
46
+ public fetchOne = <T>(sql: string, params?: any[], defaultValue?: T) =>
47
+ this.fetch<T>(sql, params).then((res) => {
48
+ if (res === undefined || res[0] === undefined) {
49
+ // Makes it as non-null result.
50
+ return defaultValue || ({} as any as T);
51
+ }
52
+ return res[0];
53
+ });
54
+
55
+ public beginTransaction = () =>
56
+ new Promise<void>(async (resolve, reject) => {
57
+ const connection = this.prepareConnection();
58
+ await this.tryToInitializeSchema(false);
59
+
60
+ connection.beginTransaction((err: MysqlError) => {
61
+ if (err) {
62
+ reject(err);
63
+ return;
64
+ }
65
+ resolve();
66
+ });
67
+ });
68
+
69
+ public commit = () =>
70
+ new Promise<void>(async (resolve, reject) => {
71
+ const connection = this.prepareConnection();
72
+ await this.tryToInitializeSchema(false);
73
+
74
+ connection.commit((err: MysqlError) => {
75
+ if (err) {
76
+ reject(err);
77
+ return;
78
+ }
79
+ resolve();
80
+ });
81
+ });
82
+
83
+ public rollback = () =>
84
+ new Promise<void>(async (resolve, reject) => {
85
+ const connection = this.prepareConnection();
86
+ await this.tryToInitializeSchema(false);
87
+
88
+ connection.rollback((err: MysqlError) => {
89
+ if (err) {
90
+ reject(err);
91
+ return;
92
+ }
93
+ resolve();
94
+ });
95
+ });
96
+
97
+ public clearConnection = () => {
98
+ if (this.connection) {
99
+ this.connection.end();
100
+ this.connection = undefined;
101
+ logger.verbose('Connection is end');
102
+ }
103
+ };
104
+
105
+ public onPluginCreated = async () => this.tryToInitializeSchema(true);
106
+
107
+ private prepareConnection = () => {
108
+ if (this.connection) {
109
+ return this.connection;
110
+ }
111
+ this.connection = createConnection(this.pluginConfig.config);
112
+ this.connection.connect();
113
+ return this.connection;
114
+ };
115
+
116
+ private changeDatabase = (dbName: string) =>
117
+ new Promise<void>((resolve, reject) =>
118
+ this.prepareConnection().changeUser(
119
+ {
120
+ database: dbName,
121
+ },
122
+ (err) => (err ? reject(err) : resolve(undefined)),
123
+ ),
124
+ );
125
+
126
+ private tryToInitializeSchema = async (initial: boolean) => {
127
+ const {
128
+ eager = false,
129
+ ignoreError = false,
130
+ database = '',
131
+ tables = {},
132
+ } = this.pluginConfig.schema || {};
133
+ if (initial && !eager) {
134
+ return;
135
+ }
136
+
137
+ // This method can be called twice when eager option is on,
138
+ // so this flag should be set and checked at first.
139
+ if (this.initialized) {
140
+ return;
141
+ }
142
+ this.initialized = true;
143
+
144
+ try {
145
+ if (database) {
146
+ logger.debug(`Prepare a database[${this.dbName}]`);
147
+ logger.stupid(this.dbName!, database);
148
+ const result = await this.query(database);
149
+ logger.debug(
150
+ `Database[${this.dbName}] is initialized: ${JSON.stringify(result)}`,
151
+ );
152
+ }
153
+ if (this.dbName) {
154
+ await this.changeDatabase(this.dbName);
155
+ logger.verbose(`Database[${this.dbName}] is connected.`);
156
+ }
157
+
158
+ for (const [name, query] of Object.entries(tables)) {
159
+ logger.debug(`Prepare a table[${name}]`);
160
+ logger.stupid(name, query);
161
+ const result = await this.query(query);
162
+ logger.debug(
163
+ `Table[${name}] is initialized: ${JSON.stringify(result)}`,
164
+ );
165
+ }
166
+ logger.verbose(`Database schema is initialized.`);
167
+ } catch (error) {
168
+ logger.warn(error);
169
+ if (!ignoreError) {
170
+ throw error;
171
+ }
172
+ }
173
+ };
174
+ }
@@ -0,0 +1,14 @@
1
+ export { ConnectionProxy } from './connectionProxy';
2
+ export {
3
+ sql,
4
+ type DeleteQueryBuilder,
5
+ type Expression,
6
+ type ExpressionBuilder,
7
+ type InsertQueryBuilder,
8
+ type NotNull,
9
+ type RawBuilder,
10
+ type SelectQueryBuilder,
11
+ type SqlBool,
12
+ type SQLClient,
13
+ type UpdateQueryBuilder,
14
+ } from './sqlClient';
@@ -0,0 +1,95 @@
1
+ import {
2
+ HandleEmptyInListsPlugin,
3
+ Kysely,
4
+ MysqlDialect,
5
+ MysqlPool,
6
+ replaceWithNoncontingentExpression,
7
+ } from 'kysely';
8
+ import {
9
+ createConnection,
10
+ type Connection,
11
+ type ConnectionOptions,
12
+ type QueryError,
13
+ } from 'mysql2';
14
+
15
+ interface LazyMysqlPoolConnection extends Connection {
16
+ release: () => void;
17
+ }
18
+
19
+ class LazyConnectionPool implements MysqlPool {
20
+ private connection: LazyMysqlPoolConnection | null = null;
21
+
22
+ constructor(private config: ConnectionOptions) {}
23
+
24
+ public getConnection = (
25
+ callback: (error: unknown, connection: LazyMysqlPoolConnection) => void,
26
+ ): void => {
27
+ if (this.connection) {
28
+ callback(null, this.connection);
29
+ return;
30
+ }
31
+ const conn = createConnection(this.config);
32
+ conn.connect((err: QueryError) => {
33
+ if (err) {
34
+ callback(err, {} as LazyMysqlPoolConnection);
35
+ return;
36
+ }
37
+ this.connection = this._addRelease(conn);
38
+ callback(null, this.connection);
39
+ });
40
+ };
41
+
42
+ public end = (callback: (error: unknown) => void): void => {
43
+ if (this.connection) {
44
+ this.connection.end((err: QueryError) => {
45
+ this.connection = null;
46
+ callback(err);
47
+ });
48
+ } else {
49
+ callback(null);
50
+ }
51
+ };
52
+
53
+ private _addRelease = (connection: Connection): LazyMysqlPoolConnection =>
54
+ Object.assign(connection, {
55
+ release: () => {},
56
+ });
57
+ }
58
+
59
+ export class SQLClient<T = unknown> extends Kysely<T> {
60
+ private pool: LazyConnectionPool;
61
+
62
+ constructor(config: ConnectionOptions) {
63
+ const pool = new LazyConnectionPool(config);
64
+ super({
65
+ dialect: new MysqlDialect({
66
+ pool,
67
+ }),
68
+ plugins: [
69
+ new HandleEmptyInListsPlugin({
70
+ strategy: replaceWithNoncontingentExpression,
71
+ }),
72
+ ],
73
+ });
74
+ this.pool = pool;
75
+ }
76
+
77
+ public clearConnection = () =>
78
+ new Promise<void>((resolve) => {
79
+ this.pool.end(() => resolve());
80
+ });
81
+ }
82
+
83
+ export {
84
+ expressionBuilder,
85
+ sql,
86
+ type DeleteQueryBuilder,
87
+ type Expression,
88
+ type ExpressionBuilder,
89
+ type InsertQueryBuilder,
90
+ type NotNull,
91
+ type RawBuilder,
92
+ type SelectQueryBuilder,
93
+ type SqlBool,
94
+ type UpdateQueryBuilder,
95
+ } from 'kysely';
@@ -1,20 +1,21 @@
1
- import build from './build';
2
-
3
- import aws from './aws';
4
- import logger from './logger';
5
- import mysql from './mysql';
6
- import trace from './trace';
7
-
8
- export const middleware = {
9
- build,
10
- aws,
11
- trace,
12
- logger,
13
- mysql,
14
- };
15
-
16
- export * from './base';
17
- export * from './aws';
18
- export * from './trace';
19
- export * from './logger';
20
- export * from './mysql';
1
+ import build from './build';
2
+
3
+ import aws from './aws';
4
+ import logger from './logger';
5
+ import mysql from './mysql';
6
+ import trace from './trace';
7
+
8
+ export const middleware = {
9
+ build,
10
+ aws,
11
+ trace,
12
+ logger,
13
+ mysql,
14
+ };
15
+
16
+ export * from './aws';
17
+ export * from './base';
18
+ export * from './database/index';
19
+ export * from './logger';
20
+ export * from './mysql';
21
+ export * from './trace';