@renderinc/sdk 0.2.0 → 0.2.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/CHANGELOG.md +9 -2
- package/README.md +32 -22
- package/dist/experimental/experimental.d.ts +6 -2
- package/dist/experimental/experimental.d.ts.map +1 -1
- package/dist/experimental/experimental.js +9 -3
- package/dist/experimental/index.d.ts +2 -2
- package/dist/experimental/index.d.ts.map +1 -1
- package/dist/experimental/index.js +6 -5
- package/dist/experimental/object/api.d.ts +11 -0
- package/dist/experimental/object/api.d.ts.map +1 -0
- package/dist/experimental/object/api.js +44 -0
- package/dist/experimental/object/client.d.ts +21 -0
- package/dist/experimental/object/client.d.ts.map +1 -0
- package/dist/experimental/object/client.js +127 -0
- package/dist/experimental/object/index.d.ts +5 -0
- package/dist/experimental/object/index.d.ts.map +1 -0
- package/dist/experimental/object/index.js +8 -0
- package/dist/experimental/object/types.d.ts +49 -0
- package/dist/experimental/object/types.d.ts.map +1 -0
- package/dist/generated/schema.d.ts +131 -3
- package/dist/generated/schema.d.ts.map +1 -1
- package/dist/workflows/uds.d.ts.map +1 -1
- package/dist/workflows/uds.js +26 -51
- package/examples/client/main.ts +1 -1
- package/examples/client/package-lock.json +4 -4
- package/examples/client/package.json +1 -1
- package/examples/task/main.ts +1 -1
- package/examples/task/package-lock.json +5 -6
- package/examples/task/package.json +1 -1
- package/package.json +9 -8
- package/src/errors.test.ts +75 -0
- package/src/experimental/experimental.ts +30 -7
- package/src/experimental/index.ts +18 -18
- package/src/experimental/{blob → object}/api.ts +7 -7
- package/src/experimental/object/client.test.ts +138 -0
- package/src/experimental/{blob → object}/client.ts +57 -57
- package/src/experimental/object/index.ts +22 -0
- package/src/experimental/object/types.test.ts +87 -0
- package/src/experimental/{blob → object}/types.ts +30 -30
- package/src/generated/schema.ts +217 -9
- package/src/utils/get-base-url.test.ts +58 -0
- package/src/workflows/client/client.test.ts +68 -0
- package/src/workflows/types.test.ts +52 -0
- package/src/workflows/uds.ts +29 -69
- package/tsconfig.json +1 -1
- package/{vite.config.ts → vitest.config.ts} +1 -0
- package/dist/workflows/client/errors.d.ts +0 -25
- package/dist/workflows/client/errors.d.ts.map +0 -1
- package/dist/workflows/client/errors.js +0 -56
- package/dist/workflows/client/schema.d.ts +0 -9322
- package/dist/workflows/client/schema.d.ts.map +0 -1
- package/dist/workflows/client/workflows.d.ts +0 -15
- package/dist/workflows/client/workflows.d.ts.map +0 -1
- package/dist/workflows/client/workflows.js +0 -63
- package/src/experimental/blob/index.ts +0 -22
- /package/dist/{workflows/client/schema.js → experimental/object/types.js} +0 -0
package/src/generated/schema.ts
CHANGED
|
@@ -27,6 +27,28 @@ export interface paths {
|
|
|
27
27
|
patch?: never;
|
|
28
28
|
trace?: never;
|
|
29
29
|
};
|
|
30
|
+
"/blueprints/validate": {
|
|
31
|
+
parameters: {
|
|
32
|
+
query?: never;
|
|
33
|
+
header?: never;
|
|
34
|
+
path?: never;
|
|
35
|
+
cookie?: never;
|
|
36
|
+
};
|
|
37
|
+
get?: never;
|
|
38
|
+
put?: never;
|
|
39
|
+
/**
|
|
40
|
+
* Validate Blueprint
|
|
41
|
+
* @description Validate a `render.yaml` Blueprint file without creating or modifying any resources. This endpoint checks the syntax and structure of the Blueprint, validates that all required fields are present, and returns a plan indicating the resources that would be created.
|
|
42
|
+
*
|
|
43
|
+
* Requests to this endpoint use `Content-Type: multipart/form-data`. The provided Blueprint file cannot exceed 10MB in size.
|
|
44
|
+
*/
|
|
45
|
+
post: operations["validate-blueprint"];
|
|
46
|
+
delete?: never;
|
|
47
|
+
options?: never;
|
|
48
|
+
head?: never;
|
|
49
|
+
patch?: never;
|
|
50
|
+
trace?: never;
|
|
51
|
+
};
|
|
30
52
|
"/owners/{ownerId}/members/{userId}": {
|
|
31
53
|
parameters: {
|
|
32
54
|
query?: never;
|
|
@@ -512,7 +534,7 @@ export interface paths {
|
|
|
512
534
|
put?: never;
|
|
513
535
|
/**
|
|
514
536
|
* Create service
|
|
515
|
-
* @description
|
|
537
|
+
* @description Creates a new Render service in the specified workspace with the specified configuration.
|
|
516
538
|
*/
|
|
517
539
|
post: operations["create-service"];
|
|
518
540
|
delete?: never;
|
|
@@ -3262,6 +3284,31 @@ export interface paths {
|
|
|
3262
3284
|
patch?: never;
|
|
3263
3285
|
trace?: never;
|
|
3264
3286
|
};
|
|
3287
|
+
"/blobs/{ownerId}/{region}": {
|
|
3288
|
+
parameters: {
|
|
3289
|
+
query?: never;
|
|
3290
|
+
header?: never;
|
|
3291
|
+
path: {
|
|
3292
|
+
/** @description The ID of the workspace to return resources for */
|
|
3293
|
+
ownerId: components["parameters"]["ownerIdPathParam"];
|
|
3294
|
+
/** @description The region to return resources for */
|
|
3295
|
+
region: components["parameters"]["regionPathParam"];
|
|
3296
|
+
};
|
|
3297
|
+
cookie?: never;
|
|
3298
|
+
};
|
|
3299
|
+
/**
|
|
3300
|
+
* List blobs
|
|
3301
|
+
* @description List blobs in the specified region for a workspace.
|
|
3302
|
+
*/
|
|
3303
|
+
get: operations["list-blobs"];
|
|
3304
|
+
put?: never;
|
|
3305
|
+
post?: never;
|
|
3306
|
+
delete?: never;
|
|
3307
|
+
options?: never;
|
|
3308
|
+
head?: never;
|
|
3309
|
+
patch?: never;
|
|
3310
|
+
trace?: never;
|
|
3311
|
+
};
|
|
3265
3312
|
"/blobs/{ownerId}/{region}/{key}": {
|
|
3266
3313
|
parameters: {
|
|
3267
3314
|
query?: never;
|
|
@@ -3603,23 +3650,25 @@ export interface components {
|
|
|
3603
3650
|
};
|
|
3604
3651
|
servicePOST: {
|
|
3605
3652
|
type: components["schemas"]["serviceType"];
|
|
3653
|
+
/** @description The service's name. Must be unique within the workspace. */
|
|
3606
3654
|
name: string;
|
|
3655
|
+
/** @description The ID of the workspace the service belongs to. Obtain your workspace's ID from its Settings page in the Render Dashboard. */
|
|
3607
3656
|
ownerId: string;
|
|
3608
3657
|
/**
|
|
3609
|
-
* @description Do not
|
|
3658
|
+
* @description The service's repository URL. Do not specify a branch in this string (use the `branch` parameter instead).
|
|
3610
3659
|
* @example https://github.com/render-examples/flask-hello-world
|
|
3611
3660
|
*/
|
|
3612
3661
|
repo?: string;
|
|
3613
3662
|
autoDeploy?: components["schemas"]["autoDeploy"];
|
|
3614
3663
|
autoDeployTrigger?: components["schemas"]["autoDeployTrigger"];
|
|
3615
|
-
/** @description
|
|
3664
|
+
/** @description The repo branch to pull, build, and deploy. If omitted, uses the repository's default branch. */
|
|
3616
3665
|
branch?: string;
|
|
3617
3666
|
image?: components["schemas"]["image"];
|
|
3618
3667
|
buildFilter?: components["schemas"]["buildFilter"];
|
|
3619
3668
|
rootDir?: string;
|
|
3620
3669
|
envVars?: components["schemas"]["envVarInputArray"];
|
|
3621
3670
|
secretFiles?: components["schemas"]["secretFileInput"][];
|
|
3622
|
-
/** @description The ID of the environment the service
|
|
3671
|
+
/** @description The ID of the environment the service belongs to, if any. Obtain an environment's ID from its Settings page in the Render Dashboard. */
|
|
3623
3672
|
environmentId?: string;
|
|
3624
3673
|
serviceDetails?: components["schemas"]["staticSiteDetailsPOST"] | components["schemas"]["webServiceDetailsPOST"] | components["schemas"]["privateServiceDetailsPOST"] | components["schemas"]["backgroundWorkerDetailsPOST"] | components["schemas"]["cronJobDetailsPOST"];
|
|
3625
3674
|
};
|
|
@@ -3656,7 +3705,11 @@ export interface components {
|
|
|
3656
3705
|
maintenanceMode?: components["schemas"]["maintenanceMode"];
|
|
3657
3706
|
/** @description Defaults to 1 */
|
|
3658
3707
|
numInstances?: number;
|
|
3659
|
-
|
|
3708
|
+
/**
|
|
3709
|
+
* @description The instance type to use. If omitted, defaults to `starter` when creating a new service.
|
|
3710
|
+
* @default starter
|
|
3711
|
+
*/
|
|
3712
|
+
plan: components["schemas"]["plan"];
|
|
3660
3713
|
preDeployCommand?: string;
|
|
3661
3714
|
pullRequestPreviewsEnabled?: components["schemas"]["pullRequestPreviewsEnabled"];
|
|
3662
3715
|
previews?: components["schemas"]["previews"];
|
|
@@ -3742,7 +3795,7 @@ export interface components {
|
|
|
3742
3795
|
envSpecificDetails?: components["schemas"]["envSpecificDetailsPATCH"];
|
|
3743
3796
|
healthCheckPath?: string;
|
|
3744
3797
|
maintenanceMode?: components["schemas"]["maintenanceMode"];
|
|
3745
|
-
plan?: components["schemas"]["
|
|
3798
|
+
plan?: components["schemas"]["plan"];
|
|
3746
3799
|
preDeployCommand?: string;
|
|
3747
3800
|
pullRequestPreviewsEnabled?: components["schemas"]["pullRequestPreviewsEnabled"];
|
|
3748
3801
|
previews?: components["schemas"]["previews"];
|
|
@@ -3807,7 +3860,7 @@ export interface components {
|
|
|
3807
3860
|
plan?: components["schemas"]["plan"];
|
|
3808
3861
|
};
|
|
3809
3862
|
/**
|
|
3810
|
-
* @description The instance type to use
|
|
3863
|
+
* @description The instance type to use. Note that base services on any paid instance type can't create preview instances with the `free` instance type.
|
|
3811
3864
|
* @example starter
|
|
3812
3865
|
* @enum {string}
|
|
3813
3866
|
*/
|
|
@@ -4250,6 +4303,18 @@ export interface components {
|
|
|
4250
4303
|
redisPlan: "free" | "starter" | "standard" | "pro" | "pro_plus" | "custom";
|
|
4251
4304
|
/** @enum {string} */
|
|
4252
4305
|
databaseStatus: "creating" | "available" | "unavailable" | "config_restart" | "suspended" | "maintenance_scheduled" | "maintenance_in_progress" | "recovery_failed" | "recovery_in_progress" | "unknown" | "updating_instance";
|
|
4306
|
+
/**
|
|
4307
|
+
* @description Controls deployment behavior when triggering a deploy.
|
|
4308
|
+
*
|
|
4309
|
+
* - `deploy_only`: Deploy the last successful build without rebuilding (minimizes downtime)
|
|
4310
|
+
* - `build_and_deploy`: Build new code and deploy it (default behavior when not specified)
|
|
4311
|
+
*
|
|
4312
|
+
* **Note:** `deploy_only` cannot be combined with `commitId`, `imageUrl` or `clearCache` parameters,
|
|
4313
|
+
* as those are build related fields.
|
|
4314
|
+
* @default build_and_deploy
|
|
4315
|
+
* @enum {string}
|
|
4316
|
+
*/
|
|
4317
|
+
DeployMode: "deploy_only" | "build_and_deploy";
|
|
4253
4318
|
projectWithCursor: {
|
|
4254
4319
|
project: components["schemas"]["project"];
|
|
4255
4320
|
cursor: components["schemas"]["cursor"];
|
|
@@ -4526,7 +4591,7 @@ export interface components {
|
|
|
4526
4591
|
/** @enum {string} */
|
|
4527
4592
|
databaseRole: "primary" | "replica";
|
|
4528
4593
|
/**
|
|
4529
|
-
* @description Defaults to
|
|
4594
|
+
* @description Defaults to `starter` when creating a new database.
|
|
4530
4595
|
* @default starter
|
|
4531
4596
|
* @enum {string}
|
|
4532
4597
|
*/
|
|
@@ -4603,6 +4668,48 @@ export interface components {
|
|
|
4603
4668
|
/** Format: date-time */
|
|
4604
4669
|
lastSync?: string;
|
|
4605
4670
|
};
|
|
4671
|
+
validateBlueprintRequest: {
|
|
4672
|
+
/**
|
|
4673
|
+
* @description The ID of the workspace to validate against. Obtain your workspace ID from its Settings page in the Render Dashboard.
|
|
4674
|
+
* @example tea-cjnxpkdhshc73d12t9i0
|
|
4675
|
+
*/
|
|
4676
|
+
ownerId: string;
|
|
4677
|
+
/**
|
|
4678
|
+
* Format: binary
|
|
4679
|
+
* @description The render.yaml file to validate, as a binary file.
|
|
4680
|
+
*/
|
|
4681
|
+
file: string;
|
|
4682
|
+
};
|
|
4683
|
+
validationError: {
|
|
4684
|
+
/** @description The path to the field with the error (e.g., `services[0].plan`) */
|
|
4685
|
+
path?: string;
|
|
4686
|
+
/** @description The error message */
|
|
4687
|
+
error: string;
|
|
4688
|
+
/** @description The line number in the YAML file (1-indexed) */
|
|
4689
|
+
line?: number;
|
|
4690
|
+
/** @description The column number in the YAML file (1-indexed) */
|
|
4691
|
+
column?: number;
|
|
4692
|
+
};
|
|
4693
|
+
validationPlanSummary: {
|
|
4694
|
+
/** @description The names of services that would be created as part of the Blueprint. */
|
|
4695
|
+
services?: string[];
|
|
4696
|
+
/** @description The names of Render Postgres databases that would be created as part of the Blueprint. */
|
|
4697
|
+
databases?: string[];
|
|
4698
|
+
/** @description The names of Render Key Value instances that would be created as part of the Blueprint. */
|
|
4699
|
+
keyValue?: string[];
|
|
4700
|
+
/** @description The names of environment groups that would be created as part of the Blueprint. */
|
|
4701
|
+
envGroups?: string[];
|
|
4702
|
+
/** @description The total number of actions that would be performed by the Blueprint. In addition to created resources, this includes modifications to individual configuration fields. */
|
|
4703
|
+
totalActions?: number;
|
|
4704
|
+
};
|
|
4705
|
+
validateBlueprintResponse: {
|
|
4706
|
+
/** @description If `true`, the Blueprint validated successfully. If `false`, at least one validation error occurred. */
|
|
4707
|
+
valid: boolean;
|
|
4708
|
+
/** @description A list of validation errors. Only present if `valid` is `false`. */
|
|
4709
|
+
errors?: components["schemas"]["validationError"][];
|
|
4710
|
+
/** @description A summary of the resources that would be created as part of the Blueprint. Only present if `valid` is `true`. */
|
|
4711
|
+
plan?: components["schemas"]["validationPlanSummary"];
|
|
4712
|
+
};
|
|
4606
4713
|
resourceRef: {
|
|
4607
4714
|
id: string;
|
|
4608
4715
|
name: string;
|
|
@@ -5182,7 +5289,7 @@ export interface components {
|
|
|
5182
5289
|
* @description Provider to send metrics to
|
|
5183
5290
|
* @enum {string}
|
|
5184
5291
|
*/
|
|
5185
|
-
otelProviderType: "BETTER_STACK" | "GRAFANA" | "DATADOG" | "NEW_RELIC" | "HONEYCOMB" | "SIGNOZ" | "CUSTOM";
|
|
5292
|
+
otelProviderType: "BETTER_STACK" | "GRAFANA" | "DATADOG" | "NEW_RELIC" | "HONEYCOMB" | "SIGNOZ" | "GROUNDSOURCE" | "CUSTOM";
|
|
5186
5293
|
metricsStream: {
|
|
5187
5294
|
/** @description The ID of the owner */
|
|
5188
5295
|
ownerId: string;
|
|
@@ -5493,6 +5600,34 @@ export interface components {
|
|
|
5493
5600
|
retries: number;
|
|
5494
5601
|
attempts: components["schemas"]["TaskAttemptDetails"][];
|
|
5495
5602
|
};
|
|
5603
|
+
blobMetadata: {
|
|
5604
|
+
/**
|
|
5605
|
+
* @description The blob's object key
|
|
5606
|
+
* @example workflow-data/task-output.json
|
|
5607
|
+
*/
|
|
5608
|
+
key: string;
|
|
5609
|
+
/**
|
|
5610
|
+
* Format: int64
|
|
5611
|
+
* @description Size of the blob in bytes
|
|
5612
|
+
* @example 1048576
|
|
5613
|
+
*/
|
|
5614
|
+
sizeBytes: number;
|
|
5615
|
+
/**
|
|
5616
|
+
* Format: date-time
|
|
5617
|
+
* @description When the blob was last modified (ISO 8601)
|
|
5618
|
+
* @example 2026-01-15T12:30:00Z
|
|
5619
|
+
*/
|
|
5620
|
+
lastModified: string;
|
|
5621
|
+
/**
|
|
5622
|
+
* @description MIME type of the blob
|
|
5623
|
+
* @example application/json
|
|
5624
|
+
*/
|
|
5625
|
+
contentType: string;
|
|
5626
|
+
};
|
|
5627
|
+
blobWithCursor: {
|
|
5628
|
+
cursor: string;
|
|
5629
|
+
blob: components["schemas"]["blobMetadata"];
|
|
5630
|
+
};
|
|
5496
5631
|
getBlobOutput: {
|
|
5497
5632
|
/**
|
|
5498
5633
|
* Format: uri
|
|
@@ -5984,6 +6119,35 @@ export interface operations {
|
|
|
5984
6119
|
503: components["responses"]["503ServiceUnavailable"];
|
|
5985
6120
|
};
|
|
5986
6121
|
};
|
|
6122
|
+
"validate-blueprint": {
|
|
6123
|
+
parameters: {
|
|
6124
|
+
query?: never;
|
|
6125
|
+
header?: never;
|
|
6126
|
+
path?: never;
|
|
6127
|
+
cookie?: never;
|
|
6128
|
+
};
|
|
6129
|
+
requestBody: {
|
|
6130
|
+
content: {
|
|
6131
|
+
"multipart/form-data": components["schemas"]["validateBlueprintRequest"];
|
|
6132
|
+
};
|
|
6133
|
+
};
|
|
6134
|
+
responses: {
|
|
6135
|
+
/** @description Validation complete */
|
|
6136
|
+
200: {
|
|
6137
|
+
headers: {
|
|
6138
|
+
[name: string]: unknown;
|
|
6139
|
+
};
|
|
6140
|
+
content: {
|
|
6141
|
+
"application/json": components["schemas"]["validateBlueprintResponse"];
|
|
6142
|
+
};
|
|
6143
|
+
};
|
|
6144
|
+
400: components["responses"]["400BadRequest"];
|
|
6145
|
+
401: components["responses"]["401Unauthorized"];
|
|
6146
|
+
403: components["responses"]["403Forbidden"];
|
|
6147
|
+
429: components["responses"]["429RateLimit"];
|
|
6148
|
+
500: components["responses"]["500InternalServerError"];
|
|
6149
|
+
};
|
|
6150
|
+
};
|
|
5987
6151
|
"remove-workspace-member": {
|
|
5988
6152
|
parameters: {
|
|
5989
6153
|
query?: never;
|
|
@@ -7357,6 +7521,14 @@ export interface operations {
|
|
|
7357
7521
|
* The host, repository, and image name all must match the currently configured image for the service.
|
|
7358
7522
|
*/
|
|
7359
7523
|
imageUrl?: string;
|
|
7524
|
+
/**
|
|
7525
|
+
* @description Deployment mode controlling build and deploy behavior.
|
|
7526
|
+
*
|
|
7527
|
+
* Defaults to `build_and_deploy` when not specified.
|
|
7528
|
+
*
|
|
7529
|
+
* **Validation:** `deploy_mode` cannot be combined with `commitId` or `imageUrl` or `clearCache`.
|
|
7530
|
+
*/
|
|
7531
|
+
deployMode?: components["schemas"]["DeployMode"];
|
|
7360
7532
|
};
|
|
7361
7533
|
};
|
|
7362
7534
|
};
|
|
@@ -12619,6 +12791,42 @@ export interface operations {
|
|
|
12619
12791
|
503: components["responses"]["503ServiceUnavailable"];
|
|
12620
12792
|
};
|
|
12621
12793
|
};
|
|
12794
|
+
"list-blobs": {
|
|
12795
|
+
parameters: {
|
|
12796
|
+
query?: {
|
|
12797
|
+
/** @description The position in the result list to start from when fetching paginated results. For details, see [Pagination](https://api-docs.render.com/reference/pagination). */
|
|
12798
|
+
cursor?: components["parameters"]["cursorParam"];
|
|
12799
|
+
/** @description The maximum number of items to return. For details, see [Pagination](https://api-docs.render.com/reference/pagination). */
|
|
12800
|
+
limit?: components["parameters"]["limitParam"];
|
|
12801
|
+
};
|
|
12802
|
+
header?: never;
|
|
12803
|
+
path: {
|
|
12804
|
+
/** @description The ID of the workspace to return resources for */
|
|
12805
|
+
ownerId: components["parameters"]["ownerIdPathParam"];
|
|
12806
|
+
/** @description The region to return resources for */
|
|
12807
|
+
region: components["parameters"]["regionPathParam"];
|
|
12808
|
+
};
|
|
12809
|
+
cookie?: never;
|
|
12810
|
+
};
|
|
12811
|
+
requestBody?: never;
|
|
12812
|
+
responses: {
|
|
12813
|
+
/** @description OK */
|
|
12814
|
+
200: {
|
|
12815
|
+
headers: {
|
|
12816
|
+
[name: string]: unknown;
|
|
12817
|
+
};
|
|
12818
|
+
content: {
|
|
12819
|
+
"application/json": components["schemas"]["blobWithCursor"][];
|
|
12820
|
+
};
|
|
12821
|
+
};
|
|
12822
|
+
401: components["responses"]["401Unauthorized"];
|
|
12823
|
+
403: components["responses"]["403Forbidden"];
|
|
12824
|
+
404: components["responses"]["404NotFound"];
|
|
12825
|
+
429: components["responses"]["429RateLimit"];
|
|
12826
|
+
500: components["responses"]["500InternalServerError"];
|
|
12827
|
+
503: components["responses"]["503ServiceUnavailable"];
|
|
12828
|
+
};
|
|
12829
|
+
};
|
|
12622
12830
|
"get-blob": {
|
|
12623
12831
|
parameters: {
|
|
12624
12832
|
query?: never;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getBaseUrl } from "./get-base-url.js";
|
|
2
|
+
|
|
3
|
+
describe("getBaseUrl", () => {
|
|
4
|
+
const originalEnv = process.env;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
process.env = { ...originalEnv };
|
|
8
|
+
process.env.RENDER_USE_LOCAL_DEV = undefined;
|
|
9
|
+
process.env.RENDER_LOCAL_DEV_URL = undefined;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
process.env = originalEnv;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("returns production URL by default", () => {
|
|
17
|
+
expect(getBaseUrl()).toBe("https://api.render.com");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns custom baseUrl when provided", () => {
|
|
21
|
+
expect(getBaseUrl({ baseUrl: "https://custom.api.com" })).toBe("https://custom.api.com");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns local dev URL when useLocalDev is true", () => {
|
|
25
|
+
expect(getBaseUrl({ useLocalDev: true })).toBe("http://localhost:8120");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("respects custom localDevUrl", () => {
|
|
29
|
+
expect(getBaseUrl({ useLocalDev: true, localDevUrl: "http://localhost:9000" })).toBe(
|
|
30
|
+
"http://localhost:9000",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("respects RENDER_USE_LOCAL_DEV env var", () => {
|
|
35
|
+
process.env.RENDER_USE_LOCAL_DEV = "true";
|
|
36
|
+
expect(getBaseUrl()).toBe("http://localhost:8120");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("respects RENDER_LOCAL_DEV_URL env var", () => {
|
|
40
|
+
process.env.RENDER_USE_LOCAL_DEV = "true";
|
|
41
|
+
process.env.RENDER_LOCAL_DEV_URL = "http://localhost:7777";
|
|
42
|
+
expect(getBaseUrl()).toBe("http://localhost:7777");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("option overrides env var for localDevUrl", () => {
|
|
46
|
+
process.env.RENDER_USE_LOCAL_DEV = "true";
|
|
47
|
+
process.env.RENDER_LOCAL_DEV_URL = "http://localhost:7777";
|
|
48
|
+
expect(getBaseUrl({ useLocalDev: true, localDevUrl: "http://localhost:9999" })).toBe(
|
|
49
|
+
"http://localhost:9999",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("ignores baseUrl option when in local dev mode", () => {
|
|
54
|
+
expect(getBaseUrl({ useLocalDev: true, baseUrl: "https://custom.api.com" })).toBe(
|
|
55
|
+
"http://localhost:8120",
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Client } from "openapi-fetch";
|
|
2
|
+
import { AbortError, ClientError, ServerError } from "../../errors.js";
|
|
3
|
+
import type { paths } from "../../generated/schema.js";
|
|
4
|
+
import { WorkflowsClient } from "./client.js";
|
|
5
|
+
|
|
6
|
+
describe("WorkflowsClient", () => {
|
|
7
|
+
describe("runTask", () => {
|
|
8
|
+
it("throws AbortError if signal already aborted", async () => {
|
|
9
|
+
// No API calls occur; client throws before using the client
|
|
10
|
+
const mockApiClient = {} as unknown as Client<paths>;
|
|
11
|
+
const client = new WorkflowsClient(mockApiClient, "http://test", "token");
|
|
12
|
+
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
controller.abort();
|
|
15
|
+
await expect(client.runTask("task-1", ["data"], controller.signal)).rejects.toBeInstanceOf(
|
|
16
|
+
AbortError,
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("handleApiError (via getTaskRun)", () => {
|
|
22
|
+
it("throws ServerError for 5xx status", async () => {
|
|
23
|
+
const mockApiClient = {
|
|
24
|
+
GET: vi.fn().mockResolvedValue({
|
|
25
|
+
error: { message: "Internal error" },
|
|
26
|
+
response: { status: 500 },
|
|
27
|
+
}),
|
|
28
|
+
} as unknown as Client<paths>;
|
|
29
|
+
|
|
30
|
+
const client = new WorkflowsClient(mockApiClient, "http://test", "token");
|
|
31
|
+
|
|
32
|
+
await expect(client.getTaskRun("run-123")).rejects.toBeInstanceOf(ServerError);
|
|
33
|
+
await expect(client.getTaskRun("run-123")).rejects.toMatchObject({
|
|
34
|
+
statusCode: 500,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("throws ClientError for 4xx status", async () => {
|
|
39
|
+
const mockApiClient = {
|
|
40
|
+
GET: vi.fn().mockResolvedValue({
|
|
41
|
+
error: { message: "Not found" },
|
|
42
|
+
response: { status: 404 },
|
|
43
|
+
}),
|
|
44
|
+
} as unknown as Client<paths>;
|
|
45
|
+
|
|
46
|
+
const client = new WorkflowsClient(mockApiClient, "http://test", "token");
|
|
47
|
+
|
|
48
|
+
await expect(client.getTaskRun("run-123")).rejects.toBeInstanceOf(ClientError);
|
|
49
|
+
await expect(client.getTaskRun("run-123")).rejects.toMatchObject({
|
|
50
|
+
statusCode: 404,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("listTaskRuns", () => {
|
|
56
|
+
it("throws ClientError for 400 status", async () => {
|
|
57
|
+
const mockApiClient = {
|
|
58
|
+
GET: vi.fn().mockResolvedValue({
|
|
59
|
+
error: { message: "Bad request" },
|
|
60
|
+
response: { status: 400 },
|
|
61
|
+
}),
|
|
62
|
+
} as unknown as Client<paths>;
|
|
63
|
+
|
|
64
|
+
const client = new WorkflowsClient(mockApiClient, "http://test", "token");
|
|
65
|
+
await expect(client.listTaskRuns({ taskId: ["task-1"] })).rejects.toBeInstanceOf(ClientError);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { TaskContext, TaskFunction, TaskResult } from "./types.js";
|
|
2
|
+
|
|
3
|
+
describe("TaskFunction type", () => {
|
|
4
|
+
it("accepts typed args and returns typed result", () => {
|
|
5
|
+
expectTypeOf<TaskFunction<[string, number], boolean>>().toExtend<
|
|
6
|
+
(a: string, b: number) => boolean | Promise<boolean>
|
|
7
|
+
>();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("defaults to any[] args and any result", () => {
|
|
11
|
+
expectTypeOf<TaskFunction>().toExtend<(...args: any[]) => any>();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("allows async return type", () => {
|
|
15
|
+
const asyncFn: TaskFunction<[string], number> = async (s) => s.length;
|
|
16
|
+
expectTypeOf(asyncFn).returns.toExtend<number | Promise<number>>();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("allows sync return type", () => {
|
|
20
|
+
const syncFn: TaskFunction<[string], number> = (s) => s.length;
|
|
21
|
+
expectTypeOf(syncFn).returns.toExtend<number | Promise<number>>();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("TaskResult type", () => {
|
|
26
|
+
it("get() returns Promise<T>", () => {
|
|
27
|
+
expectTypeOf<TaskResult<string>>().toHaveProperty("get");
|
|
28
|
+
expectTypeOf<TaskResult<string>["get"]>().returns.toEqualTypeOf<Promise<string>>();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("preserves generic parameter", () => {
|
|
32
|
+
type NumberResult = TaskResult<number>;
|
|
33
|
+
type StringResult = TaskResult<string>;
|
|
34
|
+
expectTypeOf<NumberResult["get"]>().returns.toEqualTypeOf<Promise<number>>();
|
|
35
|
+
expectTypeOf<StringResult["get"]>().returns.toEqualTypeOf<Promise<string>>();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("TaskContext type", () => {
|
|
40
|
+
it("has executeTask method", () => {
|
|
41
|
+
expectTypeOf<TaskContext>().toHaveProperty("executeTask");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("executeTask is callable", () => {
|
|
45
|
+
type ExecuteTask = TaskContext["executeTask"];
|
|
46
|
+
expectTypeOf<ExecuteTask>().toBeCallableWith(
|
|
47
|
+
{} as TaskFunction<[string], number>,
|
|
48
|
+
"taskName",
|
|
49
|
+
"arg",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
package/src/workflows/uds.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import http from "node:http";
|
|
2
2
|
import { getUserAgent } from "../version.js";
|
|
3
3
|
|
|
4
|
-
const CONTENT_LENGTH_REGEX = /Content-Length:\s*(\d+)/i;
|
|
5
|
-
|
|
6
4
|
import type {
|
|
7
5
|
CallbackRequest,
|
|
8
6
|
GetInputResponse,
|
|
@@ -97,83 +95,45 @@ export class UDSClient {
|
|
|
97
95
|
*/
|
|
98
96
|
private async request<T>(path: string, method: string, body?: any): Promise<T> {
|
|
99
97
|
return new Promise((resolve, reject) => {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// Check if we have received the full response
|
|
116
|
-
if (!headersParsed) {
|
|
117
|
-
const headerEndIndex = data.indexOf("\r\n\r\n");
|
|
118
|
-
if (headerEndIndex !== -1) {
|
|
119
|
-
headersParsed = true;
|
|
120
|
-
bodyStartIndex = headerEndIndex + 4;
|
|
121
|
-
|
|
122
|
-
// Parse Content-Length header
|
|
123
|
-
const headers = data.substring(0, headerEndIndex);
|
|
124
|
-
const contentLengthMatch = headers.match(CONTENT_LENGTH_REGEX);
|
|
125
|
-
if (contentLengthMatch) {
|
|
126
|
-
contentLength = Number.parseInt(contentLengthMatch[1], 10);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Check if we have received the complete body
|
|
132
|
-
if (headersParsed && contentLength !== null) {
|
|
133
|
-
const bodyReceived = data.length - bodyStartIndex;
|
|
134
|
-
if (bodyReceived >= contentLength) {
|
|
135
|
-
// We have the complete response, close the connection
|
|
136
|
-
client.end();
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
client.on("end", () => {
|
|
142
|
-
try {
|
|
143
|
-
// Parse HTTP response
|
|
144
|
-
const lines = data.split("\r\n");
|
|
145
|
-
const statusLine = lines[0];
|
|
146
|
-
const statusCode = Number.parseInt(statusLine.split(" ")[1], 10);
|
|
98
|
+
const req = http.request(
|
|
99
|
+
{
|
|
100
|
+
socketPath: this.socketPath,
|
|
101
|
+
path: path,
|
|
102
|
+
method: method,
|
|
103
|
+
headers: {
|
|
104
|
+
"Content-Length": body ? JSON.stringify(body).length : 0,
|
|
105
|
+
"Content-Type": "application/json",
|
|
106
|
+
"User-Agent": getUserAgent(),
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
async (res) => {
|
|
110
|
+
const chunks: Buffer[] = [];
|
|
111
|
+
for await (const chunk of res) chunks.push(chunk);
|
|
112
|
+
const responseBody = Buffer.concat(chunks).toString();
|
|
147
113
|
|
|
148
|
-
if (statusCode >= 400) {
|
|
149
|
-
reject(new Error(`HTTP ${statusCode}: ${
|
|
114
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
115
|
+
reject(new Error(`HTTP ${res.statusCode}: ${responseBody}`));
|
|
150
116
|
return;
|
|
151
117
|
}
|
|
152
118
|
|
|
153
|
-
|
|
154
|
-
const emptyLineIndex = lines.indexOf("");
|
|
155
|
-
if (emptyLineIndex === -1) {
|
|
119
|
+
if (responseBody.length === 0) {
|
|
156
120
|
resolve(undefined as T);
|
|
157
121
|
return;
|
|
158
122
|
}
|
|
159
123
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
resolve(undefined as T);
|
|
165
|
-
return;
|
|
124
|
+
try {
|
|
125
|
+
resolve(JSON.parse(responseBody));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
reject(error);
|
|
166
128
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
reject(error);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
client.on("error", (error) => {
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
req.on("error", (error) => {
|
|
175
132
|
reject(error);
|
|
176
133
|
});
|
|
134
|
+
|
|
135
|
+
// Write the body to the request
|
|
136
|
+
req.end(body ? JSON.stringify(body) : "");
|
|
177
137
|
});
|
|
178
138
|
}
|
|
179
139
|
}
|
package/tsconfig.json
CHANGED