core-services-sdk 1.3.80 → 1.3.82

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": "core-services-sdk",
3
- "version": "1.3.80",
3
+ "version": "1.3.82",
4
4
  "main": "src/index.js",
5
5
  "type": "module",
6
6
  "types": "types/index.d.ts",
@@ -49,14 +49,14 @@
49
49
  "zod": "^4.3.6"
50
50
  },
51
51
  "devDependencies": {
52
- "@vitest/coverage-v8": "^3.2.4",
53
- "eslint": "^9.30.0",
52
+ "@vitest/coverage-v8": "^4.0.18",
53
+ "eslint": "^10.0.1",
54
54
  "eslint-config-prettier": "^10.1.5",
55
55
  "eslint-plugin-prettier": "^5.5.1",
56
56
  "path": "^0.12.7",
57
57
  "prettier": "^3.6.2",
58
58
  "typescript": "^5.9.2",
59
59
  "url": "^0.11.4",
60
- "vitest": "^3.2.4"
60
+ "vitest": "^4.0.18"
61
61
  }
62
62
  }
package/src/http/http.js CHANGED
@@ -7,6 +7,50 @@
7
7
  * @module http
8
8
  */
9
9
 
10
+ /**
11
+ * @typedef {'json' | 'xml' | 'text' | 'raw' | 'file'} ResponseTypeValue
12
+ */
13
+
14
+ /**
15
+ * @typedef {Object} HttpGetOptions
16
+ * @property {string} url - The URL to send the request to.
17
+ * @property {Record<string, string>} [headers] - Optional HTTP headers.
18
+ * @property {RequestInit} [extraParams] - Additional fetch options.
19
+ * @property {ResponseTypeValue} [expectedType] - Expected response type.
20
+ */
21
+
22
+ /**
23
+ * @typedef {Object} HttpPostOptions
24
+ * @property {string} url - The URL to send the request to.
25
+ * @property {any} body - The request body to send.
26
+ * @property {Record<string, string>} [headers] - Optional HTTP headers.
27
+ * @property {ResponseTypeValue} [expectedType] - Expected response type.
28
+ */
29
+
30
+ /**
31
+ * @typedef {Object} HttpPutOptions
32
+ * @property {string} url - The URL to send the request to.
33
+ * @property {any} body - The request body to send.
34
+ * @property {Record<string, string>} [headers] - Optional HTTP headers.
35
+ * @property {ResponseTypeValue} [expectedType] - Expected response type.
36
+ */
37
+
38
+ /**
39
+ * @typedef {Object} HttpPatchOptions
40
+ * @property {string} url - The URL to send the request to.
41
+ * @property {any} body - The request body to send.
42
+ * @property {Record<string, string>} [headers] - Optional HTTP headers.
43
+ * @property {ResponseTypeValue} [expectedType] - Expected response type.
44
+ */
45
+
46
+ /**
47
+ * @typedef {Object} HttpDeleteOptions
48
+ * @property {string} url - The URL to send the request to.
49
+ * @property {any} [body] - Optional request body to send.
50
+ * @property {Record<string, string>} [headers] - Optional HTTP headers.
51
+ * @property {ResponseTypeValue} [expectedType] - Expected response type.
52
+ */
53
+
10
54
  import httpStatus from 'http-status'
11
55
  import { parseStringPromise } from 'xml2js'
12
56
 
@@ -25,7 +69,7 @@ const JSON_HEADER = {
25
69
  * @returns {boolean}
26
70
  */
27
71
  const isOkStatus = ({ status }) =>
28
- status >= httpStatus.OK && status < httpStatus.MULTIPLE_CHOICES
72
+ status >= httpStatus.OK && status < httpStatus.BAD_REQUEST
29
73
 
30
74
  /**
31
75
  * Ensures the response has a successful status, otherwise throws HttpError.
@@ -121,6 +165,8 @@ const getResponsePayload = async (response, responseType) => {
121
165
  return tryGetJsonResponse(response)
122
166
  case ResponseType.xml:
123
167
  return tryGetXmlResponse(response)
168
+ case ResponseType.raw:
169
+ return response
124
170
  default:
125
171
  return getTextResponse(response)
126
172
  }
@@ -128,13 +174,19 @@ const getResponsePayload = async (response, responseType) => {
128
174
 
129
175
  /**
130
176
  * Sends an HTTP GET request.
177
+ *
178
+ * @param {HttpGetOptions} options - The request options.
179
+ * @returns {Promise<any>} The parsed response based on expectedType.
180
+ * @throws {HttpError} If the response status is not successful.
131
181
  */
132
182
  export const get = async ({
133
183
  url,
134
184
  headers = {},
185
+ extraParams = {},
135
186
  expectedType = ResponseType.json,
136
187
  }) => {
137
188
  const response = await fetch(url, {
189
+ ...extraParams,
138
190
  method: HTTP_METHODS.GET,
139
191
  headers: { ...JSON_HEADER, ...headers },
140
192
  })
@@ -144,6 +196,10 @@ export const get = async ({
144
196
 
145
197
  /**
146
198
  * Sends an HTTP POST request.
199
+ *
200
+ * @param {HttpPostOptions} options - The request options.
201
+ * @returns {Promise<any>} The parsed response based on expectedType.
202
+ * @throws {HttpError} If the response status is not successful.
147
203
  */
148
204
  export const post = async ({
149
205
  url,
@@ -162,6 +218,10 @@ export const post = async ({
162
218
 
163
219
  /**
164
220
  * Sends an HTTP PUT request.
221
+ *
222
+ * @param {HttpPutOptions} options - The request options.
223
+ * @returns {Promise<any>} The parsed response based on expectedType.
224
+ * @throws {HttpError} If the response status is not successful.
165
225
  */
166
226
  export const put = async ({
167
227
  url,
@@ -172,7 +232,7 @@ export const put = async ({
172
232
  const response = await fetch(url, {
173
233
  method: HTTP_METHODS.PUT,
174
234
  headers: { ...JSON_HEADER, ...headers },
175
- body: JSON.stringify(body),
235
+ body: expectedType === ResponseType.json ? JSON.stringify(body) : body,
176
236
  })
177
237
  await checkStatus(response)
178
238
  return getResponsePayload(response, expectedType)
@@ -180,6 +240,10 @@ export const put = async ({
180
240
 
181
241
  /**
182
242
  * Sends an HTTP PATCH request.
243
+ *
244
+ * @param {HttpPatchOptions} options - The request options.
245
+ * @returns {Promise<any>} The parsed response based on expectedType.
246
+ * @throws {HttpError} If the response status is not successful.
183
247
  */
184
248
  export const patch = async ({
185
249
  url,
@@ -198,6 +262,10 @@ export const patch = async ({
198
262
 
199
263
  /**
200
264
  * Sends an HTTP DELETE request.
265
+ *
266
+ * @param {HttpDeleteOptions} options - The request options.
267
+ * @returns {Promise<any>} The parsed response based on expectedType.
268
+ * @throws {HttpError} If the response status is not successful.
201
269
  */
202
270
  export const deleteApi = async ({
203
271
  url,
@@ -215,7 +283,15 @@ export const deleteApi = async ({
215
283
  }
216
284
 
217
285
  /**
218
- * Consolidated HTTP client.
286
+ * Consolidated HTTP client with methods for common HTTP operations.
287
+ *
288
+ * @type {{
289
+ * get: (options: HttpGetOptions) => Promise<any>,
290
+ * put: (options: HttpPutOptions) => Promise<any>,
291
+ * post: (options: HttpPostOptions) => Promise<any>,
292
+ * patch: (options: HttpPatchOptions) => Promise<any>,
293
+ * deleteApi: (options: HttpDeleteOptions) => Promise<any>
294
+ * }}
219
295
  */
220
296
  export const http = {
221
297
  get,
@@ -10,6 +10,7 @@
10
10
  */
11
11
  export const ResponseType = Object.freeze({
12
12
  xml: 'xml',
13
+ raw: 'raw',
13
14
  json: 'json',
14
15
  text: 'text',
15
16
  file: 'file',
@@ -5,4 +5,6 @@ export * from './filters/apply-filter.js'
5
5
  export * from './modifiers/apply-order-by.js'
6
6
  export * from './start-stop-postgres-docker.js'
7
7
  export * from './modifiers/apply-pagination.js'
8
+ export * from './repositories/BaseRepository.js'
8
9
  export * from './filters/apply-filter-snake-case.js'
10
+ export * from './repositories/TenantScopedRepository.js'
@@ -75,6 +75,7 @@ export async function sqlPaginate({
75
75
  limit = 10,
76
76
  filter = {},
77
77
  snakeCase = true,
78
+ distinctOn,
78
79
  }) {
79
80
  const listQuery = baseQuery.clone()
80
81
  const countQuery = baseQuery.clone()
@@ -89,7 +90,9 @@ export async function sqlPaginate({
89
90
 
90
91
  const [rows, countResult] = await Promise.all([
91
92
  listQuery.select('*'),
92
- countQuery.count('* as count').first(),
93
+ distinctOn
94
+ ? countQuery.countDistinct(`${distinctOn} as count`).first()
95
+ : countQuery.count('* as count').first(),
93
96
  ])
94
97
 
95
98
  const totalCount = normalizeNumberOrDefault(countResult?.count || 0)
@@ -0,0 +1,95 @@
1
+ import { sqlPaginate } from '../pagination/paginate.js'
2
+ import { toSnakeCase } from '../../core/case-mapper.js'
3
+
4
+ /**
5
+ * BaseRepository
6
+ *
7
+ * Provides:
8
+ * - tableName getter (static + instance support)
9
+ * - baseQuery(options)
10
+ * - constructor-level query shaping (optional)
11
+ * - generic find() with pagination
12
+ *
13
+ * Does NOT enforce tenant isolation.
14
+ */
15
+
16
+ export class BaseRepository {
17
+ constructor({ db, log, baseQueryBuilder } = {}) {
18
+ if (!db) {
19
+ throw new Error('BaseRepository requires db instance')
20
+ }
21
+
22
+ this.db = db
23
+ this.log = log
24
+ this._baseQueryBuilder = baseQueryBuilder
25
+ }
26
+
27
+ /**
28
+ * Each concrete repository must define:
29
+ * static tableName = 'table_name'
30
+ */
31
+ get tableName() {
32
+ if (!this.constructor.tableName) {
33
+ throw new Error(`tableName not defined for ${this.constructor.name}`)
34
+ }
35
+
36
+ return this.constructor.tableName
37
+ }
38
+
39
+ /**
40
+ * Builds the base knex query.
41
+ * Applies constructor-level baseQueryBuilder if provided.
42
+ */
43
+ baseQuery(options = {}, params) {
44
+ const { trx } = options
45
+ const connection = trx || this.db
46
+
47
+ const qb = connection(this.tableName)
48
+
49
+ if (this._baseQueryBuilder) {
50
+ // Pass full params for optional dynamic shaping
51
+ return this._baseQueryBuilder(qb, params)
52
+ }
53
+
54
+ return qb
55
+ }
56
+
57
+ /**
58
+ * Generic paginated find
59
+ */
60
+ async find({
61
+ page = 1,
62
+ limit = 10,
63
+ filter = {},
64
+ orderBy = { column: 'created_at', direction: 'desc' },
65
+ options = {},
66
+ mapRow = (row) => row,
67
+ ...restParams
68
+ }) {
69
+ try {
70
+ const qb = this.baseQuery(options, {
71
+ page,
72
+ limit,
73
+ filter,
74
+ orderBy,
75
+ options,
76
+ ...restParams,
77
+ })
78
+
79
+ return await sqlPaginate({
80
+ page,
81
+ limit,
82
+ orderBy,
83
+ baseQuery: qb,
84
+ mapRow,
85
+ filter: toSnakeCase(filter),
86
+ })
87
+ } catch (error) {
88
+ if (this.log) {
89
+ this.log.error({ error }, `Error finding from ${this.tableName}`)
90
+ }
91
+
92
+ throw error
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * TenantScopedRepository
3
+ *
4
+ * Extends BaseRepository
5
+ * Enforces tenantId presence inside filter.
6
+ *
7
+ * Does NOT modify filter.
8
+ * Does NOT rebuild filter.
9
+ * Only validates existence.
10
+ */
11
+
12
+ import { BaseRepository } from './BaseRepository.js'
13
+
14
+ export class TenantScopedRepository extends BaseRepository {
15
+ /**
16
+ * Extracts and validates tenantId from filter
17
+ */
18
+ getRequiredTenantId(filter = {}) {
19
+ const { tenantId } = filter
20
+
21
+ if (!tenantId) {
22
+ throw new Error(
23
+ `Scoped repository (${this.tableName}), tenantId is required`,
24
+ )
25
+ }
26
+
27
+ return tenantId
28
+ }
29
+
30
+ /**
31
+ * Overrides find to enforce tenant presence
32
+ */
33
+ async find(params = {}) {
34
+ const { filter = {} } = params
35
+
36
+ this.getRequiredTenantId(filter)
37
+
38
+ return super.find(params)
39
+ }
40
+ }
@@ -77,7 +77,6 @@ for (const scenario of scenarios) {
77
77
 
78
78
  const result = prepareEnvValidation(definition, scenario.values, { mask })
79
79
 
80
- // פה הבחירה שלך מה לעשות עם זה
81
80
  console.log(result.table)
82
81
 
83
82
  if (!result.success) {
@@ -149,5 +149,39 @@ describe('http client (native fetch)', () => {
149
149
 
150
150
  expect(result).toHaveProperty('note')
151
151
  })
152
+
153
+ it('should return raw response object if expectedType is raw', async () => {
154
+ const mockResponse = createMockResponse({ body: 'raw data', status: 200 })
155
+ mockFetch.mockResolvedValueOnce(mockResponse)
156
+
157
+ const result = await http.get({
158
+ url: 'http://test.com',
159
+ expectedType: ResponseType.raw,
160
+ })
161
+
162
+ expect(result).toBe(mockResponse)
163
+ expect(result.status).toBe(200)
164
+ })
165
+ })
166
+
167
+ describe('PUT with non-JSON body', () => {
168
+ it('should not stringify body when expectedType is not json', async () => {
169
+ const rawBody = 'plain text body'
170
+ mockFetch.mockResolvedValueOnce(createMockResponse({ body: 'OK' }))
171
+
172
+ await http.put({
173
+ url: 'http://test.com',
174
+ body: rawBody,
175
+ expectedType: ResponseType.text,
176
+ })
177
+
178
+ expect(mockFetch).toHaveBeenCalledWith(
179
+ 'http://test.com',
180
+ expect.objectContaining({
181
+ method: 'PUT',
182
+ body: rawBody,
183
+ }),
184
+ )
185
+ })
152
186
  })
153
187
  })
@@ -7,6 +7,7 @@ describe('ResponseType', () => {
7
7
  it('should contain correct response type mappings', () => {
8
8
  expect(ResponseType).toEqual({
9
9
  xml: 'xml',
10
+ raw: 'raw',
10
11
  json: 'json',
11
12
  text: 'text',
12
13
  file: 'file',
@@ -32,7 +33,7 @@ describe('ResponseType', () => {
32
33
 
33
34
  it('should include expected keys', () => {
34
35
  const keys = Object.keys(ResponseType)
35
- expect(keys).toEqual(['xml', 'json', 'text', 'file'])
36
+ expect(keys).toEqual(['xml', 'raw', 'json', 'text', 'file'])
36
37
  })
37
38
 
38
39
  it('should include expected values', () => {
@@ -41,5 +42,6 @@ describe('ResponseType', () => {
41
42
  expect(values).toContain('xml')
42
43
  expect(values).toContain('text')
43
44
  expect(values).toContain('file')
45
+ expect(values).toContain('raw')
44
46
  })
45
47
  })
@@ -17,9 +17,9 @@ vi.mock('../../src/mailer/transport.factory.js', async () => {
17
17
 
18
18
  // Mock Mailer class
19
19
  vi.mock('../../src/mailer/mailer.service.js', async () => {
20
- const Mailer = vi.fn().mockImplementation((transport) => ({
21
- send: vi.fn((opts) => transport.sendMail(opts)),
22
- }))
20
+ const Mailer = vi.fn(function (transport) {
21
+ this.send = vi.fn((opts) => transport.sendMail(opts))
22
+ })
23
23
  return { Mailer }
24
24
  })
25
25
 
@@ -30,8 +30,12 @@ vi.mock('@sendgrid/mail', () => {
30
30
  // ---------------- Mock AWS SESv2 ----------------
31
31
  vi.mock('@aws-sdk/client-sesv2', () => {
32
32
  const mockSesInstance = { mocked: true }
33
- const SESv2Client = vi.fn(() => mockSesInstance)
34
- const SendEmailCommand = vi.fn(() => 'mocked-command')
33
+ const SESv2Client = vi.fn(function () {
34
+ Object.assign(this, mockSesInstance)
35
+ })
36
+ const SendEmailCommand = vi.fn(function () {
37
+ return 'mocked-command'
38
+ })
35
39
 
36
40
  globalThis.__mockedSesv2Client__ = SESv2Client
37
41
  globalThis.__mockedSesv2Instance__ = mockSesInstance
@@ -137,12 +141,10 @@ describe('TransportFactory', () => {
137
141
  })
138
142
 
139
143
  const args = nodemailer.createTransport.mock.calls[0][0]
140
- expect(args).toEqual({
141
- SES: {
142
- sesClient: globalThis.__mockedSesv2Instance__,
143
- SendEmailCommand: globalThis.__mockedSendEmailCommand__,
144
- },
145
- })
144
+ expect(args.SES.sesClient).toBeInstanceOf(globalThis.__mockedSesv2Client__)
145
+ expect(args.SES.SendEmailCommand).toBe(
146
+ globalThis.__mockedSendEmailCommand__,
147
+ )
146
148
 
147
149
  expect(transport.type).toBe('mock-transport')
148
150
  })
@@ -162,12 +164,10 @@ describe('TransportFactory', () => {
162
164
  })
163
165
 
164
166
  const args = nodemailer.createTransport.mock.calls[0][0]
165
- expect(args).toEqual({
166
- SES: {
167
- sesClient: globalThis.__mockedSesv2Instance__,
168
- SendEmailCommand: globalThis.__mockedSendEmailCommand__,
169
- },
170
- })
167
+ expect(args.SES.sesClient).toBeInstanceOf(globalThis.__mockedSesv2Client__)
168
+ expect(args.SES.SendEmailCommand).toBe(
169
+ globalThis.__mockedSendEmailCommand__,
170
+ )
171
171
 
172
172
  expect(transport.type).toBe('mock-transport')
173
173
  })
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { BaseRepository } from '../../src/postgresql/repositories/BaseRepository.js'
3
+
4
+ // 🔥 mock dependencies
5
+ vi.mock('../../src/postgresql/pagination/paginate.js', () => ({
6
+ sqlPaginate: vi.fn().mockResolvedValue({ page: 1 }),
7
+ }))
8
+
9
+ vi.mock('../../filters/apply-filter-snake-case.js', () => ({
10
+ applyFilterSnakeCase: vi.fn(),
11
+ }))
12
+
13
+ vi.mock('../../filters/apply-filter.js', () => ({
14
+ applyFilter: vi.fn(),
15
+ }))
16
+
17
+ vi.mock('../../core/normalize-premitives-types-or-default.js', () => ({
18
+ normalizeNumberOrDefault: vi.fn((v) => Number(v)),
19
+ }))
20
+
21
+ // import AFTER mocks
22
+ import { sqlPaginate } from '../../src/postgresql/pagination/paginate.js'
23
+ import { toSnakeCase } from '../../src/core/case-mapper.js'
24
+
25
+ describe('BaseRepository', () => {
26
+ let dbMock
27
+ let logMock
28
+
29
+ class TestRepo extends BaseRepository {
30
+ static tableName = 'test_table'
31
+ }
32
+
33
+ beforeEach(() => {
34
+ dbMock = vi.fn(() => ({
35
+ clone: vi.fn(),
36
+ }))
37
+
38
+ logMock = {
39
+ error: vi.fn(),
40
+ }
41
+ })
42
+
43
+ it('throws if db not provided', () => {
44
+ expect(() => new BaseRepository()).toThrow()
45
+ })
46
+
47
+ it('returns correct tableName', () => {
48
+ const repo = new TestRepo({ db: dbMock })
49
+ expect(repo.tableName).toBe('test_table')
50
+ })
51
+
52
+ it('baseQuery uses db when no trx', () => {
53
+ const repo = new TestRepo({ db: dbMock })
54
+
55
+ repo.baseQuery()
56
+
57
+ expect(dbMock).toHaveBeenCalledWith('test_table')
58
+ })
59
+
60
+ it('baseQuery uses trx when provided', () => {
61
+ const trxMock = vi.fn()
62
+ const repo = new TestRepo({ db: dbMock })
63
+
64
+ repo.baseQuery({ trx: trxMock })
65
+
66
+ expect(trxMock).toHaveBeenCalledWith('test_table')
67
+ expect(dbMock).not.toHaveBeenCalled()
68
+ })
69
+
70
+ it('baseQuery applies baseQueryBuilder', () => {
71
+ const builderMock = vi.fn((qb) => qb)
72
+ const repo = new TestRepo({
73
+ db: dbMock,
74
+ baseQueryBuilder: builderMock,
75
+ })
76
+
77
+ repo.baseQuery({}, { some: 'param' })
78
+
79
+ expect(builderMock).toHaveBeenCalled()
80
+ })
81
+
82
+ it('find calls sqlPaginate with correct params', async () => {
83
+ const repo = new TestRepo({ db: dbMock })
84
+
85
+ await repo.find({
86
+ page: 2,
87
+ limit: 5,
88
+ filter: { a: 1 },
89
+ })
90
+
91
+ expect(sqlPaginate).toHaveBeenCalled()
92
+ })
93
+
94
+ it('logs error if sqlPaginate throws', async () => {
95
+ // @ts-ignore
96
+ sqlPaginate.mockRejectedValueOnce(new Error('fail'))
97
+
98
+ const repo = new TestRepo({ db: dbMock, log: logMock })
99
+
100
+ await expect(repo.find({ filter: {} })).rejects.toThrow()
101
+
102
+ expect(logMock.error).toHaveBeenCalled()
103
+ })
104
+ })
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { TenantScopedRepository } from '../../src/postgresql/repositories/TenantScopedRepository.js'
3
+
4
+ class TestTenantRepo extends TenantScopedRepository {
5
+ static tableName = 'tenant_table'
6
+ }
7
+
8
+ describe('TenantScopedRepository', () => {
9
+ const dbMock = vi.fn(() => ({}))
10
+
11
+ it('throws if tenantId missing', async () => {
12
+ const repo = new TestTenantRepo({ db: dbMock })
13
+
14
+ await expect(repo.find({ filter: {} })).rejects.toThrow()
15
+ })
16
+
17
+ it('does not throw if tenantId exists', async () => {
18
+ const repo = new TestTenantRepo({ db: dbMock })
19
+
20
+ // Spy on BaseRepository.find instead of overriding repo.find
21
+ const superSpy = vi
22
+ .spyOn(Object.getPrototypeOf(TenantScopedRepository.prototype), 'find')
23
+ .mockResolvedValue(true)
24
+
25
+ const result = await repo.find({
26
+ filter: { tenantId: 'abc' },
27
+ })
28
+
29
+ expect(result).toBe(true)
30
+ expect(superSpy).toHaveBeenCalled()
31
+
32
+ superSpy.mockRestore()
33
+ })
34
+
35
+ it('getRequiredTenantId returns tenantId', () => {
36
+ const repo = new TestTenantRepo({ db: dbMock })
37
+
38
+ const id = repo.getRequiredTenantId({
39
+ tenantId: 'tenant-1',
40
+ })
41
+
42
+ expect(id).toBe('tenant-1')
43
+ })
44
+ })
@@ -1,10 +1,12 @@
1
1
  export function get({
2
2
  url,
3
3
  headers,
4
+ extraParams,
4
5
  expectedType,
5
6
  }: {
6
7
  url: any
7
8
  headers?: {}
9
+ extraParams?: {}
8
10
  expectedType?: 'json'
9
11
  }): Promise<any>
10
12
  export function post({
@@ -14,6 +14,7 @@ export type ResponseType = string
14
14
  */
15
15
  export const ResponseType: Readonly<{
16
16
  xml: 'xml'
17
+ raw: 'raw'
17
18
  json: 'json'
18
19
  text: 'text'
19
20
  file: 'file'
@@ -1,22 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm test:*)",
5
- "Bash(docker rm:*)",
6
- "Bash(docker run:*)",
7
- "Bash(mongosh:*)",
8
- "Bash(for:*)",
9
- "Bash(do if mongosh --port 27099 --eval \"db.runCommand({ ping: 1 })\")",
10
- "Bash(then echo \"Connected after $i attempts\")",
11
- "Bash(break)",
12
- "Bash(fi)",
13
- "Bash(echo:*)",
14
- "Bash(done)",
15
- "Bash(docker logs:*)",
16
- "Bash(docker system:*)",
17
- "Bash(docker volume prune:*)",
18
- "Bash(docker image prune:*)",
19
- "Bash(docker builder prune:*)"
20
- ]
21
- }
22
- }