@cheetah.js/orm 0.1.136 → 0.1.140

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/README.md CHANGED
@@ -39,6 +39,7 @@ export default config;
39
39
  Actually, the ORM only supports PostgreSQL, but in the future it will support other databases.
40
40
  - Entities: Path to entities. Accepts glob patterns or an array of Entity classes.
41
41
  - MigrationPath: Path to migrations. Accepts glob patterns. Is optional.
42
+ - Connection pool (optional): `max`, `idleTimeout`, `maxLifetime`, `connectionTimeout` in seconds control the Bun SQL pool. Defaults are 20 connections, 20s idle timeout, 300s max lifetime, and 10s connection timeout.
42
43
  <br/>
43
44
  <br/>
44
45
  After that, you need to import the ORM into the project and add it to the Cheetah.js instance:
@@ -1,14 +1,27 @@
1
- import { SQL } from 'bun';
2
- import { ConnectionSettings, DriverInterface, Statement, SnapshotConstraintInfo, ColDiff } from './driver.interface';
1
+ import { SQL } from "bun";
2
+ import { ConnectionSettings, DriverInterface, Statement, SnapshotConstraintInfo, ColDiff } from "./driver.interface";
3
3
  export declare abstract class BunDriverBase implements Partial<DriverInterface> {
4
4
  protected sql: SQL;
5
5
  connectionString: string;
6
- abstract readonly dbType: 'postgres' | 'mysql';
6
+ protected readonly poolOptions: {
7
+ max: number;
8
+ idleTimeout: number;
9
+ maxLifetime: number;
10
+ connectionTimeout: number;
11
+ };
12
+ abstract readonly dbType: "postgres" | "mysql";
7
13
  constructor(options: ConnectionSettings);
8
14
  protected buildConnectionString(options: ConnectionSettings): string;
9
15
  protected abstract getProtocol(): string;
10
16
  protected abstract getIdentifierQuote(): string;
11
17
  connect(): Promise<void>;
18
+ protected buildPoolOptions(options: ConnectionSettings): {
19
+ max: number;
20
+ idleTimeout: number;
21
+ maxLifetime: number;
22
+ connectionTimeout: number;
23
+ };
24
+ private createSqlInstance;
12
25
  protected validateConnection(): Promise<void>;
13
26
  disconnect(): Promise<void>;
14
27
  executeSql(sqlString: string): Promise<any>;
@@ -6,6 +6,7 @@ const transaction_context_1 = require("../transaction/transaction-context");
6
6
  class BunDriverBase {
7
7
  constructor(options) {
8
8
  this.connectionString = this.buildConnectionString(options);
9
+ this.poolOptions = this.buildPoolOptions(options);
9
10
  }
10
11
  buildConnectionString(options) {
11
12
  if (options.connectionString) {
@@ -19,23 +20,35 @@ class BunDriverBase {
19
20
  if (this.sql) {
20
21
  return;
21
22
  }
22
- this.sql = new bun_1.SQL({
23
+ this.sql = this.createSqlInstance();
24
+ await this.validateConnection();
25
+ }
26
+ buildPoolOptions(options) {
27
+ return {
28
+ max: options.max ?? undefined,
29
+ idleTimeout: options.idleTimeout ?? undefined,
30
+ maxLifetime: options.maxLifetime ?? undefined,
31
+ connectionTimeout: options.connectionTimeout ?? undefined,
32
+ };
33
+ }
34
+ createSqlInstance() {
35
+ return new bun_1.SQL({
23
36
  url: this.connectionString,
24
- max: 20,
25
- idleTimeout: 20, // segundos antes de fechar idle
26
- maxLifetime: 300, // recicla conexões a cada 5 min
27
- connectionTimeout: 10
37
+ max: this.poolOptions.max,
38
+ idleTimeout: this.poolOptions.idleTimeout,
39
+ maxLifetime: this.poolOptions.maxLifetime,
40
+ connectionTimeout: this.poolOptions.connectionTimeout,
28
41
  });
29
- await this.validateConnection();
30
42
  }
31
43
  async validateConnection() {
32
- await this.sql.unsafe('SELECT 1');
44
+ await this.sql.unsafe("SELECT 1");
33
45
  }
34
46
  async disconnect() {
35
47
  if (!this.sql) {
36
48
  return;
37
49
  }
38
50
  await this.sql.close();
51
+ this.sql = null;
39
52
  }
40
53
  async executeSql(sqlString) {
41
54
  const context = this.getExecutionContext();
@@ -47,31 +60,31 @@ class BunDriverBase {
47
60
  return txContext;
48
61
  }
49
62
  if (!this.sql) {
50
- throw new Error('Database not connected');
63
+ throw new Error("Database not connected");
51
64
  }
52
65
  return this.sql;
53
66
  }
54
67
  async transaction(callback) {
55
68
  if (!this.sql) {
56
- throw new Error('Database not connected');
69
+ throw new Error("Database not connected");
57
70
  }
58
71
  return await this.sql.begin(callback);
59
72
  }
60
73
  toDatabaseValue(value) {
61
74
  if (value === null || value === undefined) {
62
- return 'NULL';
75
+ return "NULL";
63
76
  }
64
77
  if (value instanceof Date) {
65
78
  return `'${value.toISOString()}'`;
66
79
  }
67
80
  switch (typeof value) {
68
- case 'string':
81
+ case "string":
69
82
  return `'${this.escapeString(value)}'`;
70
- case 'number':
83
+ case "number":
71
84
  return value;
72
- case 'boolean':
85
+ case "boolean":
73
86
  return value;
74
- case 'object':
87
+ case "object":
75
88
  return `'${this.escapeString(JSON.stringify(value))}'`;
76
89
  default:
77
90
  return `'${this.escapeString(String(value))}'`;
@@ -85,25 +98,25 @@ class BunDriverBase {
85
98
  }
86
99
  buildWhereClause(where) {
87
100
  if (!where) {
88
- return '';
101
+ return "";
89
102
  }
90
103
  return ` WHERE ${where}`;
91
104
  }
92
105
  buildOrderByClause(orderBy) {
93
106
  if (!orderBy || orderBy.length === 0) {
94
- return '';
107
+ return "";
95
108
  }
96
- return ` ORDER BY ${orderBy.join(', ')}`;
109
+ return ` ORDER BY ${orderBy.join(", ")}`;
97
110
  }
98
111
  buildLimitClause(limit) {
99
112
  if (!limit) {
100
- return '';
113
+ return "";
101
114
  }
102
115
  return ` LIMIT ${limit}`;
103
116
  }
104
117
  buildOffsetClause(offset) {
105
118
  if (!offset) {
106
- return '';
119
+ return "";
107
120
  }
108
121
  return ` OFFSET ${offset}`;
109
122
  }
@@ -119,23 +132,23 @@ class BunDriverBase {
119
132
  buildColumnConstraints(colDiff) {
120
133
  const parts = [];
121
134
  if (!colDiff.colChanges?.nullable) {
122
- parts.push('NOT NULL');
135
+ parts.push("NOT NULL");
123
136
  }
124
137
  if (colDiff.colChanges?.primary) {
125
- parts.push('PRIMARY KEY');
138
+ parts.push("PRIMARY KEY");
126
139
  }
127
140
  if (colDiff.colChanges?.unique) {
128
- parts.push('UNIQUE');
141
+ parts.push("UNIQUE");
129
142
  }
130
143
  if (colDiff.colChanges?.default) {
131
144
  parts.push(`DEFAULT ${colDiff.colChanges.default}`);
132
145
  }
133
- return parts.length > 0 ? ' ' + parts.join(' ') : '';
146
+ return parts.length > 0 ? " " + parts.join(" ") : "";
134
147
  }
135
148
  async executeStatement(statement) {
136
149
  const startTime = Date.now();
137
150
  const context = this.getExecutionContext();
138
- if (statement.statement === 'insert') {
151
+ if (statement.statement === "insert") {
139
152
  const sql = this.buildInsertSqlWithReturn(statement);
140
153
  const result = await context.unsafe(sql);
141
154
  return this.handleInsertReturn(statement, result, sql, startTime);
@@ -161,34 +174,34 @@ class BunDriverBase {
161
174
  buildBaseSql(statement) {
162
175
  const { statement: type, table, columns, values, alias } = statement;
163
176
  switch (type) {
164
- case 'select':
165
- return `SELECT ${columns ? columns.join(', ') : '*'} FROM ${table} ${alias}`;
166
- case 'insert':
177
+ case "select":
178
+ return `SELECT ${columns ? columns.join(", ") : "*"} FROM ${table} ${alias}`;
179
+ case "insert":
167
180
  return this.buildInsertSql(table, values, columns, alias);
168
- case 'update':
181
+ case "update":
169
182
  return this.buildUpdateSql(table, values, alias);
170
- case 'delete':
183
+ case "delete":
171
184
  return this.buildDeleteSql(table, alias);
172
- case 'count':
185
+ case "count":
173
186
  return `SELECT COUNT(*) as count FROM ${table} ${alias}`;
174
187
  default:
175
- return '';
188
+ return "";
176
189
  }
177
190
  }
178
191
  buildInsertSql(table, values, columns, alias) {
179
192
  const q = this.getIdentifierQuote();
180
193
  const fields = Object.keys(values)
181
194
  .map((v) => `${q}${v}${q}`)
182
- .join(', ');
195
+ .join(", ");
183
196
  const vals = Object.values(values)
184
197
  .map((value) => this.toDatabaseValue(value))
185
- .join(', ');
198
+ .join(", ");
186
199
  return `INSERT INTO ${table} (${fields}) VALUES (${vals})`;
187
200
  }
188
201
  buildUpdateSql(table, values, alias) {
189
202
  const sets = Object.entries(values)
190
203
  .map(([key, value]) => `${key} = ${this.toDatabaseValue(value)}`)
191
- .join(', ');
204
+ .join(", ");
192
205
  return `UPDATE ${table} as ${alias} SET ${sets}`;
193
206
  }
194
207
  buildDeleteSql(table, alias) {
@@ -196,17 +209,17 @@ class BunDriverBase {
196
209
  }
197
210
  buildJoinClause(statement) {
198
211
  if (!statement.join)
199
- return '';
212
+ return "";
200
213
  return statement.join
201
214
  .map((join) => {
202
215
  const table = `${join.joinSchema}.${join.joinTable}`;
203
216
  return ` ${join.type} JOIN ${table} ${join.joinAlias} ON ${join.on}`;
204
217
  })
205
- .join('');
218
+ .join("");
206
219
  }
207
220
  buildWhereAndOrderClauses(statement) {
208
- if (statement.statement === 'insert')
209
- return '';
221
+ if (statement.statement === "insert")
222
+ return "";
210
223
  let sql = this.buildWhereClause(statement.where);
211
224
  sql += this.buildOrderByClause(statement.orderBy);
212
225
  sql += this.buildLimitAndOffsetClause(statement);
@@ -220,7 +233,7 @@ class BunDriverBase {
220
233
  if (limit) {
221
234
  return this.buildLimitClause(limit);
222
235
  }
223
- return '';
236
+ return "";
224
237
  }
225
238
  getForeignKeysFromConstraints(constraints, row, columnNameField) {
226
239
  const columnName = row[columnNameField];
@@ -230,25 +243,28 @@ class BunDriverBase {
230
243
  .filter(Boolean);
231
244
  }
232
245
  isForeignKeyConstraint(constraint, columnName) {
233
- return (constraint.type === 'FOREIGN KEY' &&
246
+ return (constraint.type === "FOREIGN KEY" &&
234
247
  constraint.consDef.includes(columnName));
235
248
  }
236
249
  parseForeignKeyDefinition(consDef) {
237
250
  const quote = this.getIdentifierQuote();
238
251
  const escapedQuote = this.escapeRegex(quote);
239
- const pattern = new RegExp(`REFERENCES\\s+(?:${escapedQuote}([^${escapedQuote}]+)${escapedQuote}|([\\w.]+))\\s*\\(([^)]+)\\)`, 'i');
252
+ const pattern = new RegExp(`REFERENCES\\s+(?:${escapedQuote}([^${escapedQuote}]+)${escapedQuote}|([\\w.]+))\\s*\\(([^)]+)\\)`, "i");
240
253
  const match = consDef.match(pattern);
241
254
  if (!match)
242
255
  return null;
243
256
  const tableName = (match[1] || match[2]).trim();
244
- const columnName = match[3].split(',')[0].trim().replace(new RegExp(escapedQuote, 'g'), '');
257
+ const columnName = match[3]
258
+ .split(",")[0]
259
+ .trim()
260
+ .replace(new RegExp(escapedQuote, "g"), "");
245
261
  return {
246
262
  referencedColumnName: columnName,
247
- referencedTableName: tableName
263
+ referencedTableName: tableName,
248
264
  };
249
265
  }
250
266
  escapeRegex(str) {
251
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
267
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
252
268
  }
253
269
  }
254
270
  exports.BunDriverBase = BunDriverBase;
@@ -1,10 +1,10 @@
1
- import { PropertyOptions } from '../decorators/property.decorator';
2
- import { Collection } from '../domain/collection';
3
- import { Reference } from '../domain/reference';
4
- import { ValueObject } from '../common/value-object';
1
+ import { PropertyOptions } from "../decorators/property.decorator";
2
+ import { Collection } from "../domain/collection";
3
+ import { Reference } from "../domain/reference";
4
+ import { ValueObject } from "../common/value-object";
5
5
  export interface DriverInterface {
6
6
  connectionString: string;
7
- readonly dbType: 'postgres' | 'mysql';
7
+ readonly dbType: "postgres" | "mysql";
8
8
  executeStatement(statement: Statement<any>): Promise<{
9
9
  query: any;
10
10
  startTime: number;
@@ -41,7 +41,8 @@ export interface DriverInterface {
41
41
  }, schema: string | undefined, tableName: string): string;
42
42
  transaction<T>(callback: (tx: any) => Promise<T>): Promise<T>;
43
43
  }
44
- export type ValueOrInstance<T> = T extends ValueObject<any, any> ? T | T['value'] : NonNullable<T>;
44
+ export type ValueOrInstance<T> = T extends ValueObject<any, any> ? // @ts-ignore
45
+ T | T["value"] : NonNullable<T>;
45
46
  export type SnapshotConstraintInfo = {
46
47
  indexName: string;
47
48
  consDef: string;
@@ -54,6 +55,10 @@ export interface ConnectionSettings<T extends DriverInterface = DriverInterface>
54
55
  password?: string;
55
56
  database?: string;
56
57
  connectionString?: string;
58
+ max?: number;
59
+ idleTimeout?: number;
60
+ maxLifetime?: number;
61
+ connectionTimeout?: number;
57
62
  ssl?: boolean;
58
63
  driver: new (options: ConnectionSettings<T>) => T;
59
64
  entities?: Function[] | string;
@@ -85,7 +90,7 @@ export interface EnumOptions<T> extends PropertyOptions {
85
90
  array?: boolean;
86
91
  }
87
92
  export type JoinStatement<T> = {
88
- type: 'INNER' | 'LEFT' | 'RIGHT';
93
+ type: "INNER" | "LEFT" | "RIGHT";
89
94
  originalEntity?: Function;
90
95
  originTable: string;
91
96
  originSchema: string;
@@ -104,14 +109,14 @@ export type JoinStatement<T> = {
104
109
  }[];
105
110
  };
106
111
  export type Statement<T> = {
107
- statement?: 'select' | 'insert' | 'update' | 'delete' | 'count';
112
+ statement?: "select" | "insert" | "update" | "delete" | "count";
108
113
  table?: string;
109
114
  alias?: string;
110
115
  customSchema?: string;
111
116
  columns?: string[];
112
117
  join?: JoinStatement<T>[];
113
118
  selectJoin?: Statement<T>[];
114
- strategy?: 'select' | 'joined';
119
+ strategy?: "select" | "joined";
115
120
  where?: string;
116
121
  values?: any;
117
122
  groupBy?: string[];
@@ -161,10 +166,10 @@ export type ColumnsInfo = {
161
166
  precision?: number;
162
167
  scale?: number;
163
168
  isDecimal?: boolean;
164
- enumItems?: string[] | number[] | '__AUTO_DETECT__';
169
+ enumItems?: string[] | number[] | "__AUTO_DETECT__";
165
170
  foreignKeys?: ForeignKeyInfo[];
166
171
  };
167
- export type SqlActionType = 'CREATE' | 'DELETE' | 'ALTER' | 'INDEX';
172
+ export type SqlActionType = "CREATE" | "DELETE" | "ALTER" | "INDEX";
168
173
  export type ColDiff = {
169
174
  actionType: SqlActionType;
170
175
  colName: string;
@@ -209,11 +214,11 @@ export type PrimaryProperty<T> = T extends {
209
214
  [PrimaryKeyProp]?: infer PK;
210
215
  } ? PK : T extends {
211
216
  _id?: any;
212
- } ? '_id' | string : T extends {
217
+ } ? "_id" | string : T extends {
213
218
  uuid?: any;
214
- } ? 'uuid' : T extends {
219
+ } ? "uuid" : T extends {
215
220
  id?: any;
216
- } ? 'id' : never;
221
+ } ? "id" : never;
217
222
  export type IPrimaryKeyValue = number | string | bigint | Date | {
218
223
  toHexString(): string;
219
224
  };
@@ -232,7 +237,7 @@ export type OperatorMap<T> = {
232
237
  $lte?: ExpandScalar<T>;
233
238
  $like?: string;
234
239
  };
235
- export type ExcludeFunctions<T, K extends keyof T> = T[K] extends Function ? never : (K extends symbol ? never : K);
240
+ export type ExcludeFunctions<T, K extends keyof T> = T[K] extends Function ? never : K extends symbol ? never : K;
236
241
  export type Scalar = boolean | number | string | bigint | symbol | Date | RegExp | Uint8Array | {
237
242
  toHexString(): string;
238
243
  };
@@ -257,7 +262,7 @@ export type ObjectQuery<T> = ExpandObject<T> & OperatorMap<T>;
257
262
  export type FilterQuery<T> = ObjectQuery<T> | NonNullable<ExpandScalar<Primary<T>>> | NonNullable<EntityProps<T> & OperatorMap<T>> | FilterQuery<T>[];
258
263
  export type Relationship<T> = {
259
264
  isRelation?: boolean;
260
- relation: 'one-to-many' | 'many-to-one';
265
+ relation: "one-to-many" | "many-to-one";
261
266
  type: Function;
262
267
  fkKey?: (string & keyof T) | ((e: T) => any);
263
268
  entity: () => EntityName<T>;
@@ -271,7 +276,7 @@ type Loadable<T extends object> = Collection<T, any> | Reference<T> | readonly T
271
276
  type ExtractType<T> = T extends Loadable<infer U> ? U : T;
272
277
  type StringKeys<T, E extends string = never> = T extends Collection<any, any> ? `${Exclude<keyof ExtractType<T> | E, symbol>}` : T extends Reference<any> ? `${Exclude<keyof ExtractType<T> | E, symbol>}` : T extends object ? `${Exclude<keyof ExtractType<T> | E, symbol>}` : never;
273
278
  type Defined<T> = Exclude<T, null | undefined>;
274
- type GetStringKey<T, K extends StringKeys<T, string>, E extends string> = K extends keyof T ? ExtractType<T[K]> : (K extends E ? keyof T : never);
279
+ type GetStringKey<T, K extends StringKeys<T, string>, E extends string> = K extends keyof T ? ExtractType<T[K]> : K extends E ? keyof T : never;
275
280
  type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
276
281
  export type AutoPath<O, P extends string, E extends string = never, D extends Prev[number] = 5> = [D] extends [never] ? string : P extends any ? (P & `${string}.` extends never ? P : P & `${string}.`) extends infer Q ? Q extends `${infer A}.${infer B}` ? A extends StringKeys<O, E> ? `${A}.${AutoPath<Defined<GetStringKey<O, A, E>>, B, E, Prev[D]>}` : never : Q extends StringKeys<O, E> ? (Defined<GetStringKey<O, Q, E>> extends unknown ? Exclude<P, `${string}.`> : never) | (StringKeys<Defined<GetStringKey<O, Q, E>>, E> extends never ? never : `${Q}.`) : keyof ExtractType<O> : never : never;
277
282
  export declare enum QueryOrder {
@@ -297,7 +302,7 @@ export type QueryOrderKeys<T> = QueryOrderKeysFlat | QueryOrderMap<T>;
297
302
  export type QueryOrderMap<T> = {
298
303
  [K in keyof T as ExcludeFunctions<T, K>]?: QueryOrderKeys<ExpandProperty<T[K]>>;
299
304
  };
300
- export type EntityField<T, P extends string = never> = AutoPath<T, P, '*'>;
305
+ export type EntityField<T, P extends string = never> = AutoPath<T, P, "*">;
301
306
  export interface FindOptions<T, P extends string = never> {
302
307
  load?: readonly AutoPath<T, P>[];
303
308
  orderBy?: (QueryOrderMap<T> & {
@@ -308,7 +313,7 @@ export interface FindOptions<T, P extends string = never> {
308
313
  offset?: number;
309
314
  fields?: readonly EntityField<T, P>[];
310
315
  schema?: string;
311
- loadStrategy?: 'select' | 'joined';
316
+ loadStrategy?: "select" | "joined";
312
317
  }
313
- export type FindOneOption<T, P extends string = never> = Omit<FindOptions<T, P>, 'limit' | 'offset'>;
318
+ export type FindOneOption<T, P extends string = never> = Omit<FindOptions<T, P>, "limit" | "offset">;
314
319
  export {};
@@ -4,6 +4,7 @@ export declare class OrmService {
4
4
  private orm;
5
5
  private storage;
6
6
  private allEntities;
7
+ private project;
7
8
  constructor(orm: Orm, storage: EntityStorage, entityFile?: string);
8
9
  private discoverRelationshipTypes;
9
10
  private discoverEnumTypes;
@@ -58,7 +58,8 @@ let OrmService = class OrmService {
58
58
  this.orm = orm;
59
59
  this.storage = storage;
60
60
  this.allEntities = new Map();
61
- const files = new ts_morph_1.Project({ skipLoadingLibFiles: true }).addSourceFilesAtPaths(entityFile ?? this.getSourceFilePaths());
61
+ this.project = new ts_morph_1.Project({ skipLoadingLibFiles: true });
62
+ const files = this.project.addSourceFilesAtPaths(entityFile ?? this.getSourceFilePaths());
62
63
  files.forEach(file => {
63
64
  file.getClasses().forEach(classDeclaration => {
64
65
  if (classDeclaration.getDecorator('Entity')) {
@@ -281,8 +282,7 @@ let OrmService = class OrmService {
281
282
  console.log('No entities found!');
282
283
  return;
283
284
  }
284
- const files = new ts_morph_1.Project({ skipLoadingLibFiles: true })
285
- .addSourceFilesAtPaths(this.getSourceFilePaths());
285
+ const files = this.project.getSourceFiles();
286
286
  this.discoverRelationshipTypes(files);
287
287
  this.discoverEnumTypes(files, entities);
288
288
  for (const entity of entities) {
@@ -320,7 +320,23 @@ let OrmService = class OrmService {
320
320
  }
321
321
  getSourceFilePaths() {
322
322
  const projectRoot = process.cwd();
323
- const patterns = ['**/*.ts', '**/*.js', '!**/node_modules/**'];
323
+ const patterns = [
324
+ '**/*.entity.ts',
325
+ '**/entities/**/*.ts',
326
+ '**/entity/**/*.ts',
327
+ '**/*.entity.js',
328
+ '**/entities/**/*.js',
329
+ '**/entity/**/*.js',
330
+ '!**/node_modules/**',
331
+ '!**/test/**',
332
+ '!**/tests/**',
333
+ '!**/*.spec.ts',
334
+ '!**/*.test.ts',
335
+ '!**/*.spec.js',
336
+ '!**/*.test.js',
337
+ '!**/dist/**',
338
+ '!**/build/**'
339
+ ];
324
340
  try {
325
341
  return globby
326
342
  .sync(patterns, { cwd: projectRoot, gitignore: true, absolute: false })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cheetah.js/orm",
3
- "version": "0.1.136",
3
+ "version": "0.1.140",
4
4
  "description": "A simple ORM for Cheetah.js.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",
@@ -55,5 +55,5 @@
55
55
  "bun",
56
56
  "value-object"
57
57
  ],
58
- "gitHead": "2109d17dd39ca357d6a0b48bf0b7bb08270fe8b6"
58
+ "gitHead": "46e2ddb9901a113c6c865f0f10f51abb710fe3e3"
59
59
  }