@functional-systems/lambdadb 0.2.1 → 0.3.0-dev.1
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 +54 -49
- package/dist/commonjs/client.d.ts +128 -0
- package/dist/commonjs/client.d.ts.map +1 -0
- package/dist/commonjs/client.js +182 -0
- package/dist/commonjs/client.js.map +1 -0
- package/dist/commonjs/index.d.ts +2 -0
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/index.js +7 -1
- package/dist/commonjs/index.js.map +1 -1
- package/dist/esm/client.d.ts +128 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +177 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/examples/collectionScoped.example.ts +32 -0
- package/examples/collectionsList.example.ts +8 -12
- package/package.json +5 -3
- package/src/client.ts +321 -0
- package/src/index.ts +10 -0
- package/_speakeasy/.github/action-inputs-config.json +0 -53
- package/_speakeasy/.github/action-security-config.json +0 -88
package/src/client.ts
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection-scoped facade for LambdaDB API.
|
|
3
|
+
* Use this client for a better DX: no need to pass collectionName on every call.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const client = new LambdaDBClient({ projectApiKey: "..." });
|
|
7
|
+
* const collection = client.collection("my-collection");
|
|
8
|
+
* await collection.get();
|
|
9
|
+
* await collection.docs.list({ size: 20 });
|
|
10
|
+
* await collection.docs.upsert({ docs: [{ id: "1", text: "hello" }] });
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { LambdaDBCore } from "./core.js";
|
|
14
|
+
import type { SDKOptions } from "./lib/config.js";
|
|
15
|
+
import { collectionsCreate } from "./funcs/collectionsCreate.js";
|
|
16
|
+
import { collectionsDelete } from "./funcs/collectionsDelete.js";
|
|
17
|
+
import { collectionsGet } from "./funcs/collectionsGet.js";
|
|
18
|
+
import { collectionsList } from "./funcs/collectionsList.js";
|
|
19
|
+
import { collectionsQuery } from "./funcs/collectionsQuery.js";
|
|
20
|
+
import { collectionsUpdate } from "./funcs/collectionsUpdate.js";
|
|
21
|
+
import { collectionsDocsBulkUpsert } from "./funcs/collectionsDocsBulkUpsert.js";
|
|
22
|
+
import { collectionsDocsDelete } from "./funcs/collectionsDocsDelete.js";
|
|
23
|
+
import { collectionsDocsFetch } from "./funcs/collectionsDocsFetch.js";
|
|
24
|
+
import { collectionsDocsGetBulkUpsert } from "./funcs/collectionsDocsGetBulkUpsert.js";
|
|
25
|
+
import { collectionsDocsListDocs } from "./funcs/collectionsDocsListDocs.js";
|
|
26
|
+
import { collectionsDocsUpdate } from "./funcs/collectionsDocsUpdate.js";
|
|
27
|
+
import { collectionsDocsUpsert } from "./funcs/collectionsDocsUpsert.js";
|
|
28
|
+
import type { RequestOptions } from "./lib/sdks.js";
|
|
29
|
+
import type * as operations from "./models/operations/index.js";
|
|
30
|
+
import type * as models from "./models/index.js";
|
|
31
|
+
import { unwrapAsync } from "./types/fp.js";
|
|
32
|
+
|
|
33
|
+
export type { RequestOptions };
|
|
34
|
+
|
|
35
|
+
// Re-export common types for facade users
|
|
36
|
+
export type { operations, models };
|
|
37
|
+
|
|
38
|
+
/** Default base URL for the LambdaDB API. */
|
|
39
|
+
export const DEFAULT_BASE_URL = "https://api.lambdadb.ai";
|
|
40
|
+
/** Default project name when not specified. */
|
|
41
|
+
export const DEFAULT_PROJECT_NAME = "playground";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Options for LambdaDBClient. Supports baseUrl + projectName (recommended) or
|
|
45
|
+
* legacy projectHost / serverURL. When neither serverURL nor projectHost is set,
|
|
46
|
+
* the base URL is built as `${baseUrl}/projects/${projectName}`.
|
|
47
|
+
*/
|
|
48
|
+
export type LambdaDBClientOptions = SDKOptions & {
|
|
49
|
+
/**
|
|
50
|
+
* API base URL (e.g. https://api.lambdadb.ai). Default: "https://api.lambdadb.ai"
|
|
51
|
+
*/
|
|
52
|
+
baseUrl?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Project name (path segment under /projects/). Default: "playground"
|
|
55
|
+
*/
|
|
56
|
+
projectName?: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function normalizeClientOptions(
|
|
60
|
+
options: LambdaDBClientOptions = {},
|
|
61
|
+
): SDKOptions {
|
|
62
|
+
const {
|
|
63
|
+
baseUrl = DEFAULT_BASE_URL,
|
|
64
|
+
projectName = DEFAULT_PROJECT_NAME,
|
|
65
|
+
serverURL,
|
|
66
|
+
projectHost,
|
|
67
|
+
...rest
|
|
68
|
+
} = options;
|
|
69
|
+
|
|
70
|
+
if (serverURL !== undefined && serverURL !== null) {
|
|
71
|
+
return { ...rest, serverURL };
|
|
72
|
+
}
|
|
73
|
+
if (projectHost !== undefined && projectHost !== null) {
|
|
74
|
+
return { ...rest, projectHost };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
78
|
+
const serverURLFromBase = `${base}/projects/${encodeURIComponent(projectName)}`;
|
|
79
|
+
return { ...rest, serverURL: serverURLFromBase };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Client with collection-scoped API. Prefer this over the legacy
|
|
84
|
+
* `LambdaDB` when you want to avoid passing collectionName on every call.
|
|
85
|
+
*/
|
|
86
|
+
export class LambdaDBClient extends LambdaDBCore {
|
|
87
|
+
constructor(options?: LambdaDBClientOptions) {
|
|
88
|
+
super(normalizeClientOptions(options));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get a handle for a specific collection. All methods on the handle
|
|
93
|
+
* use this collection name; you do not pass it again.
|
|
94
|
+
*/
|
|
95
|
+
collection(collectionName: string): CollectionHandle {
|
|
96
|
+
return new CollectionHandle(this, collectionName);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* List all collections in the project.
|
|
101
|
+
*/
|
|
102
|
+
async listCollections(options?: RequestOptions) {
|
|
103
|
+
return unwrapAsync(collectionsList(this, options));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create a new collection.
|
|
108
|
+
*/
|
|
109
|
+
async createCollection(
|
|
110
|
+
request: operations.CreateCollectionRequest,
|
|
111
|
+
options?: RequestOptions,
|
|
112
|
+
) {
|
|
113
|
+
return unwrapAsync(collectionsCreate(this, request, options));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Handle for a single collection. All methods operate on this collection.
|
|
119
|
+
*/
|
|
120
|
+
export class CollectionHandle {
|
|
121
|
+
constructor(
|
|
122
|
+
private readonly client: LambdaDBCore,
|
|
123
|
+
readonly collectionName: string,
|
|
124
|
+
) {}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get metadata of this collection.
|
|
128
|
+
*/
|
|
129
|
+
async get(options?: RequestOptions) {
|
|
130
|
+
return unwrapAsync(
|
|
131
|
+
collectionsGet(this.client, { collectionName: this.collectionName }, options),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Configure (update) this collection.
|
|
137
|
+
*/
|
|
138
|
+
async update(
|
|
139
|
+
requestBody: operations.UpdateCollectionRequestBody,
|
|
140
|
+
options?: RequestOptions,
|
|
141
|
+
) {
|
|
142
|
+
return unwrapAsync(
|
|
143
|
+
collectionsUpdate(
|
|
144
|
+
this.client,
|
|
145
|
+
{
|
|
146
|
+
collectionName: this.collectionName,
|
|
147
|
+
requestBody,
|
|
148
|
+
},
|
|
149
|
+
options,
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Delete this collection.
|
|
156
|
+
*/
|
|
157
|
+
async delete(options?: RequestOptions) {
|
|
158
|
+
return unwrapAsync(
|
|
159
|
+
collectionsDelete(
|
|
160
|
+
this.client,
|
|
161
|
+
{ collectionName: this.collectionName },
|
|
162
|
+
options,
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Search this collection with a query.
|
|
169
|
+
*/
|
|
170
|
+
async query(
|
|
171
|
+
requestBody: operations.QueryCollectionRequestBody,
|
|
172
|
+
options?: RequestOptions,
|
|
173
|
+
) {
|
|
174
|
+
return unwrapAsync(
|
|
175
|
+
collectionsQuery(
|
|
176
|
+
this.client,
|
|
177
|
+
{
|
|
178
|
+
collectionName: this.collectionName,
|
|
179
|
+
requestBody,
|
|
180
|
+
},
|
|
181
|
+
options,
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
readonly docs: CollectionDocs = new CollectionDocs(this.client, this.collectionName);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Document operations scoped to a collection.
|
|
191
|
+
*/
|
|
192
|
+
class CollectionDocs {
|
|
193
|
+
constructor(
|
|
194
|
+
private readonly client: LambdaDBCore,
|
|
195
|
+
private readonly collectionName: string,
|
|
196
|
+
) {}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* List documents in the collection.
|
|
200
|
+
*/
|
|
201
|
+
async list(
|
|
202
|
+
params?: { size?: number; pageToken?: string },
|
|
203
|
+
options?: RequestOptions,
|
|
204
|
+
) {
|
|
205
|
+
return unwrapAsync(
|
|
206
|
+
collectionsDocsListDocs(
|
|
207
|
+
this.client,
|
|
208
|
+
{ collectionName: this.collectionName, ...params },
|
|
209
|
+
options,
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Upsert documents. Max payload 6MB.
|
|
216
|
+
*/
|
|
217
|
+
async upsert(
|
|
218
|
+
body: { docs: Array<Record<string, unknown>> },
|
|
219
|
+
options?: RequestOptions,
|
|
220
|
+
): Promise<models.MessageResponse> {
|
|
221
|
+
return unwrapAsync(
|
|
222
|
+
collectionsDocsUpsert(
|
|
223
|
+
this.client,
|
|
224
|
+
{
|
|
225
|
+
collectionName: this.collectionName,
|
|
226
|
+
requestBody: body,
|
|
227
|
+
},
|
|
228
|
+
options,
|
|
229
|
+
),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Update documents (each doc must have id). Max payload 6MB.
|
|
235
|
+
*/
|
|
236
|
+
async update(
|
|
237
|
+
body: { docs: Array<Record<string, unknown>> },
|
|
238
|
+
options?: RequestOptions,
|
|
239
|
+
): Promise<models.MessageResponse> {
|
|
240
|
+
return unwrapAsync(
|
|
241
|
+
collectionsDocsUpdate(
|
|
242
|
+
this.client,
|
|
243
|
+
{
|
|
244
|
+
collectionName: this.collectionName,
|
|
245
|
+
requestBody: body,
|
|
246
|
+
},
|
|
247
|
+
options,
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Delete documents by ids and/or filter.
|
|
254
|
+
*/
|
|
255
|
+
async delete(
|
|
256
|
+
body: operations.DeleteDocsRequestBody,
|
|
257
|
+
options?: RequestOptions,
|
|
258
|
+
): Promise<models.MessageResponse> {
|
|
259
|
+
return unwrapAsync(
|
|
260
|
+
collectionsDocsDelete(
|
|
261
|
+
this.client,
|
|
262
|
+
{
|
|
263
|
+
collectionName: this.collectionName,
|
|
264
|
+
requestBody: body,
|
|
265
|
+
},
|
|
266
|
+
options,
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Fetch documents by IDs (max 100).
|
|
273
|
+
*/
|
|
274
|
+
async fetch(
|
|
275
|
+
body: operations.FetchDocsRequestBody,
|
|
276
|
+
options?: RequestOptions,
|
|
277
|
+
) {
|
|
278
|
+
return unwrapAsync(
|
|
279
|
+
collectionsDocsFetch(
|
|
280
|
+
this.client,
|
|
281
|
+
{
|
|
282
|
+
collectionName: this.collectionName,
|
|
283
|
+
requestBody: body,
|
|
284
|
+
},
|
|
285
|
+
options,
|
|
286
|
+
),
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get presigned URL and metadata for bulk upload (up to 200MB).
|
|
292
|
+
*/
|
|
293
|
+
async getBulkUpsert(options?: RequestOptions) {
|
|
294
|
+
return unwrapAsync(
|
|
295
|
+
collectionsDocsGetBulkUpsert(
|
|
296
|
+
this.client,
|
|
297
|
+
{ collectionName: this.collectionName },
|
|
298
|
+
options,
|
|
299
|
+
),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Trigger bulk upsert with an object key from getBulkUpsert().
|
|
305
|
+
*/
|
|
306
|
+
async bulkUpsert(
|
|
307
|
+
body: { objectKey: string },
|
|
308
|
+
options?: RequestOptions,
|
|
309
|
+
): Promise<models.MessageResponse> {
|
|
310
|
+
return unwrapAsync(
|
|
311
|
+
collectionsDocsBulkUpsert(
|
|
312
|
+
this.client,
|
|
313
|
+
{
|
|
314
|
+
collectionName: this.collectionName,
|
|
315
|
+
requestBody: body,
|
|
316
|
+
},
|
|
317
|
+
options,
|
|
318
|
+
),
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,3 +7,13 @@ export * as files from "./lib/files.js";
|
|
|
7
7
|
export { HTTPClient } from "./lib/http.js";
|
|
8
8
|
export type { Fetcher, HTTPClientOptions } from "./lib/http.js";
|
|
9
9
|
export * from "./sdk/sdk.js";
|
|
10
|
+
|
|
11
|
+
/** Collection-scoped client (recommended). See docs/REFACTORING_PROPOSAL.md */
|
|
12
|
+
export {
|
|
13
|
+
LambdaDBClient,
|
|
14
|
+
CollectionHandle,
|
|
15
|
+
DEFAULT_BASE_URL,
|
|
16
|
+
DEFAULT_PROJECT_NAME,
|
|
17
|
+
type RequestOptions as ClientRequestOptions,
|
|
18
|
+
type LambdaDBClientOptions,
|
|
19
|
+
} from "./client.js";
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"name": "mode",
|
|
4
|
-
"validation_regex": "/^(direct|pr)$/.source",
|
|
5
|
-
"validation_message": "Must be `direct` or `pr`"
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
"name": "speakeasy_version",
|
|
9
|
-
"validation_regex": "/^[\\w.\\-]+$/.source",
|
|
10
|
-
"validation_message": "Letters, numbers, or .-_ only"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"name": "openapi_doc_location",
|
|
14
|
-
"validation_regex": "/^((https?):\\/\\/([\\w\\-]+\\.)+\\w+(\\/.*)?|[\\w.\\-\\/]+)$/i.source",
|
|
15
|
-
"validation_message": "Must be a valid server URL or file path containing letters, numbers, or .-_/ only"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"name": "openapi_doc_auth_header",
|
|
19
|
-
"validation_regex": "/^[A-Za-z\\-]+$/.source",
|
|
20
|
-
"validation_message": "Letters or - only"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"name": "create_release"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"name": "publish_python",
|
|
27
|
-
"language": "python"
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"name": "publish_typescript",
|
|
31
|
-
"language": "typescript"
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"name": "publish_java",
|
|
35
|
-
"language": "java"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"name": "publish_php",
|
|
39
|
-
"language": "php"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"name": "publish_ruby",
|
|
43
|
-
"language": "ruby"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"name": "publish_csharp",
|
|
47
|
-
"language": "csharp"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"name": "publish_terraform",
|
|
51
|
-
"language": "terraform"
|
|
52
|
-
}
|
|
53
|
-
]
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"name": "pypi_token",
|
|
4
|
-
"secret_name": "PYPI_TOKEN",
|
|
5
|
-
"validation_regex": "/^[\\w.\\-]+$/.source",
|
|
6
|
-
"validation_message": "Letters, numbers, or .-_ only",
|
|
7
|
-
"language": "python"
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"name": "npm_token",
|
|
11
|
-
"secret_name": "NPM_TOKEN",
|
|
12
|
-
"validation_regex": "/^[\\w.\\-]+$/.source",
|
|
13
|
-
"validation_message": "Letters, numbers, or .-_ only",
|
|
14
|
-
"language": "typescript"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"name": "packagist_username",
|
|
18
|
-
"validation_regex": "/^[\\w.\\-]+$/.source",
|
|
19
|
-
"validation_message": "Letters, numbers, or .-_ only",
|
|
20
|
-
"language": "php"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"name": "packagist_token",
|
|
24
|
-
"validation_regex": "/^[\\w.\\-]+$/.source",
|
|
25
|
-
"validation_message": "Letters, numbers, or .-_ only",
|
|
26
|
-
"language": "php"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"name": "openapi_doc_auth_token",
|
|
30
|
-
"secret_name": "SPEC_TOKEN",
|
|
31
|
-
"validation_regex": "/^[\\w.\\-]+$/.source",
|
|
32
|
-
"validation_message": "Letters, numbers, or .-_ only"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"name": "speakeasy_api_key",
|
|
36
|
-
"secret_name": "SPEAKEASY_API_KEY",
|
|
37
|
-
"validation_regex": "/^[\\w.\\-]+$/.source",
|
|
38
|
-
"validation_message": "Letters, numbers, or .-_ only"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"name": "ossrh_username",
|
|
42
|
-
"secret_name": "MAVEN_USERNAME",
|
|
43
|
-
"validation_regex": "/^[\\w.\\-]+$/.source",
|
|
44
|
-
"validation_message": "Letters, numbers, or .-_ only",
|
|
45
|
-
"language": "java"
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"name": "ossrh_password",
|
|
49
|
-
"secret_name": "MAVEN_PASSWORD",
|
|
50
|
-
"language": "java"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"name": "java_gpg_secret_key",
|
|
54
|
-
"validation_regex": "/^[\\w.\\-\\t \\r\\n]+$/.source",
|
|
55
|
-
"validation_message": "Letters, numbers, tabs, spaces, newlines, or .-_ only",
|
|
56
|
-
"language": "java"
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
"name": "java_gpg_passphrase",
|
|
60
|
-
"language": "java"
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"name": "terraform_gpg_secret_key",
|
|
64
|
-
"secret_name": "TERRAFORM_GPG_SECRET_KEY",
|
|
65
|
-
"validation_regex": "/^[\\w.\\-\\t \\r\\n]+$/.source",
|
|
66
|
-
"validation_message": "Letters, numbers, tabs, spaces, newlines, or .-_ only",
|
|
67
|
-
"language": "terraform"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"name": "terraform_gpg_passphrase",
|
|
71
|
-
"secret_name": "TERRAFORM_GPG_PASSPHRASE",
|
|
72
|
-
"language": "terraform"
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
"name": "rubygems_auth_token",
|
|
76
|
-
"secret_name": "RUBYGEMS_AUTH_TOKEN",
|
|
77
|
-
"language": "ruby"
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"name": "nuget_api_key",
|
|
81
|
-
"secret_name": "NUGET_API_KEY",
|
|
82
|
-
"language": "csharp"
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"name": "slack_webhook_url",
|
|
86
|
-
"secret_name": "SLACK_WEBHOOK_URL"
|
|
87
|
-
}
|
|
88
|
-
]
|