@superdurable/connector-productboard 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +4 -2
- package/dex-connector.json +60 -0
- package/dist/chunk-GDHSBL3L.js +106 -0
- package/dist/connection-CxSuhl7t.d.ts +84 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.js +250 -0
- package/dist/mock.d.ts +35 -0
- package/dist/mock.js +75 -0
- package/package.json +59 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Super Durable
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Productboard TypeScript connector
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This package implements the `listFeatures` Query for Dex TypeScript Flows. It reads one Productboard REST API v2 feature page and returns only feature IDs, names, and an opaque next-page cursor. The Go-recorded conformance scenarios verify exact requests, branches, retries, and outputs.
|
|
4
|
+
|
|
5
|
+
Create a Personal Access Token in [Productboard Workspace Settings > Integrations > Public APIs > Access Token](https://developer.productboard.com/reference/api-token) and enter it in Dex Web secret `api_token`. Leave `baseUrl` blank for the public API endpoint. See the [connector README](../README.md) and [runnable example](examples/feature-discovery/README.md). Real Productboard authorization remains unverified.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"connectorId": "productboard",
|
|
3
|
+
"releaseTag": "connectors/productboard/go/v0.1.0",
|
|
4
|
+
"package": {
|
|
5
|
+
"ecosystem": "npm",
|
|
6
|
+
"name": "@superdurable/connector-productboard",
|
|
7
|
+
"version": "0.1.0"
|
|
8
|
+
},
|
|
9
|
+
"factories": [
|
|
10
|
+
{
|
|
11
|
+
"name": "newListFeaturesStep",
|
|
12
|
+
"kind": "query",
|
|
13
|
+
"operationId": "listFeatures",
|
|
14
|
+
"inputType": "ListFeaturesInput",
|
|
15
|
+
"description": "List one page of feature IDs and names, with an opaque next-page cursor.",
|
|
16
|
+
"fields": {
|
|
17
|
+
"stepType": "stepType",
|
|
18
|
+
"annotations": "annotations",
|
|
19
|
+
"connectionName": "connectionName",
|
|
20
|
+
"resultAttribute": "resultAttribute"
|
|
21
|
+
},
|
|
22
|
+
"branches": [
|
|
23
|
+
{
|
|
24
|
+
"id": "listed",
|
|
25
|
+
"field": "listed",
|
|
26
|
+
"optional": false
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "providerRejected",
|
|
30
|
+
"field": "providerRejected",
|
|
31
|
+
"optional": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "invalidResponse",
|
|
35
|
+
"field": "invalidResponse",
|
|
36
|
+
"optional": true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "defect",
|
|
40
|
+
"field": "defect",
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"inputSchema": {
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"description": "ListFeaturesInput selects a page by its opaque, non-secret cursor.",
|
|
47
|
+
"properties": {
|
|
48
|
+
"pageCursor": {
|
|
49
|
+
"description": "PageCursor is blank for the first page or a previous NextCursor value.",
|
|
50
|
+
"type": "string"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"required": [
|
|
54
|
+
"pageCursor"
|
|
55
|
+
],
|
|
56
|
+
"type": "object"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// src/generated/models.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { SecretString } from "@superdurable/connectors-sdk";
|
|
4
|
+
var ConfigSchema = z.object({
|
|
5
|
+
"baseUrl": z.string().optional(),
|
|
6
|
+
"timeoutMs": z.number().int().optional(),
|
|
7
|
+
"maxResponseBytes": z.number().int().optional()
|
|
8
|
+
});
|
|
9
|
+
function decodeCredentials(value) {
|
|
10
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("credential fields are invalid");
|
|
11
|
+
const fields = value;
|
|
12
|
+
const allowed = /* @__PURE__ */ new Set(["api_token"]);
|
|
13
|
+
if (Object.keys(fields).some((key) => !allowed.has(key))) throw new TypeError("stored credential does not match the connector credential fields");
|
|
14
|
+
if (fields["api_token"] != null && !(typeof fields["api_token"] === "string")) throw new TypeError("credential api_token has an invalid type");
|
|
15
|
+
const credentials = {};
|
|
16
|
+
if (!fields["api_token"]) throw new TypeError("credential api_token is required");
|
|
17
|
+
credentials.apiToken = new SecretString(fields["api_token"] ?? "");
|
|
18
|
+
return credentials;
|
|
19
|
+
}
|
|
20
|
+
var ListFeaturesInputSchema = z.strictObject({
|
|
21
|
+
"pageCursor": z.string()
|
|
22
|
+
});
|
|
23
|
+
var FeatureMetadataSchema = z.strictObject({
|
|
24
|
+
"id": z.string(),
|
|
25
|
+
"name": z.string()
|
|
26
|
+
});
|
|
27
|
+
var FeaturesPageSchema = z.strictObject({
|
|
28
|
+
"features": z.array(FeatureMetadataSchema).nullable(),
|
|
29
|
+
"nextCursor": z.string()
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// src/generated/definitions.ts
|
|
33
|
+
var ConnectorID = "productboard";
|
|
34
|
+
var ProviderID = "productboard";
|
|
35
|
+
var ListFeaturesBranch = {
|
|
36
|
+
Listed: "listed",
|
|
37
|
+
ProviderRejected: "providerRejected",
|
|
38
|
+
InvalidResponse: "invalidResponse",
|
|
39
|
+
Defect: "defect"
|
|
40
|
+
};
|
|
41
|
+
var ListFeaturesBranchListed = ListFeaturesBranch.Listed;
|
|
42
|
+
var ListFeaturesBranchProviderRejected = ListFeaturesBranch.ProviderRejected;
|
|
43
|
+
var ListFeaturesBranchInvalidResponse = ListFeaturesBranch.InvalidResponse;
|
|
44
|
+
var ListFeaturesBranchDefect = ListFeaturesBranch.Defect;
|
|
45
|
+
var ListFeaturesDefinition = {
|
|
46
|
+
operation: { connectorId: ConnectorID, operationId: "listFeatures" },
|
|
47
|
+
branches: [
|
|
48
|
+
{ id: "listed", description: "Productboard returned one feature page." },
|
|
49
|
+
{ id: "providerRejected", description: "Productboard conclusively rejected the request.", optional: true },
|
|
50
|
+
{ id: "invalidResponse", description: "Productboard returned malformed or oversized JSON.", optional: true },
|
|
51
|
+
{ id: "defect", description: "Local input or connection credentials are invalid.", optional: true }
|
|
52
|
+
],
|
|
53
|
+
stepDefaults: { executeMethodTimeoutMs: 3e4, executeDurability: "async" }
|
|
54
|
+
};
|
|
55
|
+
var ListFeaturesMetadata = { kind: "query", idempotency: "none", authorization: "required" };
|
|
56
|
+
|
|
57
|
+
// src/generated/connection.ts
|
|
58
|
+
import { ConnectionRefSchema } from "@superdurable/connectors-sdk";
|
|
59
|
+
var Connection = class {
|
|
60
|
+
/** Stable provider and connection name. */
|
|
61
|
+
reference;
|
|
62
|
+
/** Bound provider operations. */
|
|
63
|
+
operations;
|
|
64
|
+
/** Validate a reference and bind operations. */
|
|
65
|
+
constructor(reference, operations) {
|
|
66
|
+
this.reference = ConnectionRefSchema.parse(reference);
|
|
67
|
+
if (this.reference.provider !== ProviderID) throw new TypeError("connection provider differs from the manifest");
|
|
68
|
+
if (!operations) throw new TypeError("connector operations are required");
|
|
69
|
+
this.operations = operations;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function newConnection(client, reference) {
|
|
73
|
+
if (!client) throw new TypeError("connector client is required");
|
|
74
|
+
return new Connection(reference, client);
|
|
75
|
+
}
|
|
76
|
+
function newConnectionWithOperations(operations, reference) {
|
|
77
|
+
return new Connection(reference, operations);
|
|
78
|
+
}
|
|
79
|
+
function newProjectConnection(project, connectionName, createClient) {
|
|
80
|
+
if (!project) throw new TypeError("loaded project is required");
|
|
81
|
+
const reference = ConnectionRefSchema.parse({ provider: ProviderID, name: connectionName });
|
|
82
|
+
const selected = project.configuration.connections.find((entry) => entry.connectorId === ConnectorID && entry.connectionName === connectionName);
|
|
83
|
+
if (!selected || selected.provider !== ProviderID) throw new TypeError("project connection is not configured");
|
|
84
|
+
return newConnection(createClient(project, connectionName), reference);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
ConfigSchema,
|
|
89
|
+
decodeCredentials,
|
|
90
|
+
ListFeaturesInputSchema,
|
|
91
|
+
FeatureMetadataSchema,
|
|
92
|
+
FeaturesPageSchema,
|
|
93
|
+
ConnectorID,
|
|
94
|
+
ProviderID,
|
|
95
|
+
ListFeaturesBranch,
|
|
96
|
+
ListFeaturesBranchListed,
|
|
97
|
+
ListFeaturesBranchProviderRejected,
|
|
98
|
+
ListFeaturesBranchInvalidResponse,
|
|
99
|
+
ListFeaturesBranchDefect,
|
|
100
|
+
ListFeaturesDefinition,
|
|
101
|
+
ListFeaturesMetadata,
|
|
102
|
+
Connection,
|
|
103
|
+
newConnection,
|
|
104
|
+
newConnectionWithOperations,
|
|
105
|
+
newProjectConnection
|
|
106
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { CredentialProvider, SecretString, Query, ConnectionRef } from '@superdurable/connectors-sdk';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/** Connector configuration; omitted fields use manifest defaults. */
|
|
5
|
+
interface Config {
|
|
6
|
+
/** Productboard REST API v2 root as an absolute HTTPS URL. Blank uses the displayed public endpoint; override only for a trusted local test gateway. */
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
/** Maximum duration of one API request as a positive Go duration such as 15s. Blank uses the displayed default; increase for slow pages. Runtime value in milliseconds. */
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
/** Maximum complete page response in bytes as a positive integer. Blank uses the displayed default; increase for known larger feature pages. */
|
|
11
|
+
maxResponseBytes?: number;
|
|
12
|
+
}
|
|
13
|
+
/** Runtime schema for manifest configuration. */
|
|
14
|
+
declare const ConfigSchema: z.ZodObject<{
|
|
15
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
16
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
maxResponseBytes: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
/** Private connector credentials resolved by the host. */
|
|
20
|
+
interface Credentials {
|
|
21
|
+
/** Secret Personal Access Token from Productboard Workspace Settings > Integrations > Public APIs > Access Token. Create a token, copy its opaque value into this secret field; blank prevents calls. Dex sends it only as a Bearer token. */
|
|
22
|
+
apiToken: SecretString;
|
|
23
|
+
}
|
|
24
|
+
/** Decode private credential fields for the connector. */
|
|
25
|
+
declare function decodeCredentials(value: unknown): Credentials;
|
|
26
|
+
/** Host-owned source of private connector credentials. */
|
|
27
|
+
type CredentialSource = CredentialProvider<Credentials>;
|
|
28
|
+
/** ListFeaturesInput selects a page by its opaque, non-secret cursor. */
|
|
29
|
+
interface ListFeaturesInput {
|
|
30
|
+
/** PageCursor is blank for the first page or a previous NextCursor value. */
|
|
31
|
+
"pageCursor": string;
|
|
32
|
+
}
|
|
33
|
+
/** FeatureMetadata contains only the feature ID and name. */
|
|
34
|
+
interface FeatureMetadata {
|
|
35
|
+
/** ID is the Productboard entity ID. */
|
|
36
|
+
"id": string;
|
|
37
|
+
/** Name is the feature name. */
|
|
38
|
+
"name": string;
|
|
39
|
+
}
|
|
40
|
+
/** FeaturesPage contains one page and the next opaque cursor, if any. */
|
|
41
|
+
interface FeaturesPage {
|
|
42
|
+
/** Features excludes descriptions and other provider fields. */
|
|
43
|
+
"features": Array<FeatureMetadata> | null;
|
|
44
|
+
/** NextCursor is blank at the last page. */
|
|
45
|
+
"nextCursor": string;
|
|
46
|
+
}
|
|
47
|
+
/** Runtime validator for ListFeaturesInput. */
|
|
48
|
+
declare const ListFeaturesInputSchema: z.ZodObject<{
|
|
49
|
+
pageCursor: z.ZodString;
|
|
50
|
+
}, z.core.$strict>;
|
|
51
|
+
/** Runtime validator for FeatureMetadata. */
|
|
52
|
+
declare const FeatureMetadataSchema: z.ZodObject<{
|
|
53
|
+
id: z.ZodString;
|
|
54
|
+
name: z.ZodString;
|
|
55
|
+
}, z.core.$strict>;
|
|
56
|
+
/** Runtime validator for FeaturesPage. */
|
|
57
|
+
declare const FeaturesPageSchema: z.ZodObject<{
|
|
58
|
+
features: z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
59
|
+
id: z.ZodString;
|
|
60
|
+
name: z.ZodString;
|
|
61
|
+
}, z.core.$strict>>>;
|
|
62
|
+
nextCursor: z.ZodString;
|
|
63
|
+
}, z.core.$strict>;
|
|
64
|
+
|
|
65
|
+
/** Typed operations supplied by a client or mock. */
|
|
66
|
+
interface Operations {
|
|
67
|
+
/** Invoke listFeatures through this connection. */
|
|
68
|
+
listFeatures(): Query<ListFeaturesInput, FeaturesPage>;
|
|
69
|
+
}
|
|
70
|
+
/** Named connector connection kept outside Flow state. */
|
|
71
|
+
declare class Connection<BoundOperations = Operations> {
|
|
72
|
+
/** Stable provider and connection name. */
|
|
73
|
+
readonly reference: ConnectionRef;
|
|
74
|
+
/** Bound provider operations. */
|
|
75
|
+
readonly operations: BoundOperations;
|
|
76
|
+
/** Validate a reference and bind operations. */
|
|
77
|
+
constructor(reference: ConnectionRef, operations: BoundOperations);
|
|
78
|
+
}
|
|
79
|
+
/** Bind a client to a named connection. */
|
|
80
|
+
declare function newConnection(client: Operations, reference: ConnectionRef): Connection;
|
|
81
|
+
/** Bind scripted operations to a named connection. */
|
|
82
|
+
declare function newConnectionWithOperations(operations: Operations, reference: ConnectionRef): Connection;
|
|
83
|
+
|
|
84
|
+
export { Connection as C, type FeaturesPage as F, type ListFeaturesInput as L, type Operations as O, type Config as a, type Credentials as b, ConfigSchema as c, type CredentialSource as d, type FeatureMetadata as e, FeatureMetadataSchema as f, FeaturesPageSchema as g, ListFeaturesInputSchema as h, decodeCredentials as i, newConnectionWithOperations as j, newConnection as n };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { L as ListFeaturesInput, F as FeaturesPage, C as Connection, O as Operations, a as Config, b as Credentials } from './connection-CxSuhl7t.js';
|
|
2
|
+
export { c as ConfigSchema, d as CredentialSource, e as FeatureMetadata, f as FeatureMetadataSchema, g as FeaturesPageSchema, h as ListFeaturesInputSchema, i as decodeCredentials, n as newConnection, j as newConnectionWithOperations } from './connection-CxSuhl7t.js';
|
|
3
|
+
import { QueryDefinition, QueryStepConfig, Query, QueryResult, QueryStep, CredentialProvider, Call, QueryAttempt } from '@superdurable/connectors-sdk';
|
|
4
|
+
import { StepTarget } from '@superdurable/dex';
|
|
5
|
+
import { LoadedProject } from '@superdurable/connectors-sdk/projectconfig';
|
|
6
|
+
import { ProviderRequest, ProviderResponse } from '@superdurable/connectors-sdk/providerhttp';
|
|
7
|
+
import 'zod';
|
|
8
|
+
|
|
9
|
+
/** Stable connector identity. */
|
|
10
|
+
declare const ConnectorID = "productboard";
|
|
11
|
+
/** Provider identity for named connections. */
|
|
12
|
+
declare const ProviderID = "productboard";
|
|
13
|
+
/** Branch IDs for listFeatures. */
|
|
14
|
+
declare const ListFeaturesBranch: {
|
|
15
|
+
readonly Listed: "listed";
|
|
16
|
+
readonly ProviderRejected: "providerRejected";
|
|
17
|
+
readonly InvalidResponse: "invalidResponse";
|
|
18
|
+
readonly Defect: "defect";
|
|
19
|
+
};
|
|
20
|
+
/** Productboard returned one feature page. */
|
|
21
|
+
declare const ListFeaturesBranchListed: "listed";
|
|
22
|
+
/** Productboard conclusively rejected the request. */
|
|
23
|
+
declare const ListFeaturesBranchProviderRejected: "providerRejected";
|
|
24
|
+
/** Productboard returned malformed or oversized JSON. */
|
|
25
|
+
declare const ListFeaturesBranchInvalidResponse: "invalidResponse";
|
|
26
|
+
/** Local input or connection credentials are invalid. */
|
|
27
|
+
declare const ListFeaturesBranchDefect: "defect";
|
|
28
|
+
/** Manifest definition for listFeatures. */
|
|
29
|
+
declare const ListFeaturesDefinition: QueryDefinition;
|
|
30
|
+
/** Manifest metadata for listFeatures. */
|
|
31
|
+
declare const ListFeaturesMetadata: {
|
|
32
|
+
readonly kind: "query";
|
|
33
|
+
readonly idempotency: "none";
|
|
34
|
+
readonly authorization: "required";
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Configuration for the listFeatures connector Step. */
|
|
38
|
+
interface ListFeaturesStepConfig<StepInput, OperationInput = ListFeaturesInput, OperationOutput = FeaturesPage> extends Omit<QueryStepConfig<StepInput, OperationInput, OperationOutput>, 'operation' | 'connection' | 'branches'> {
|
|
39
|
+
/** Named connection supplying this operation. */
|
|
40
|
+
connection: Connection<{
|
|
41
|
+
listFeatures(): Query<OperationInput, OperationOutput>;
|
|
42
|
+
}>;
|
|
43
|
+
/** Name matching the bound connection. */
|
|
44
|
+
connectionName: string;
|
|
45
|
+
/** Productboard returned one feature page. */
|
|
46
|
+
listed: StepTarget<QueryResult<OperationOutput>>;
|
|
47
|
+
/** Productboard conclusively rejected the request. */
|
|
48
|
+
providerRejected?: StepTarget<QueryResult<OperationOutput>>;
|
|
49
|
+
/** Productboard returned malformed or oversized JSON. */
|
|
50
|
+
invalidResponse?: StepTarget<QueryResult<OperationOutput>>;
|
|
51
|
+
/** Local input or connection credentials are invalid. */
|
|
52
|
+
defect?: StepTarget<QueryResult<OperationOutput>>;
|
|
53
|
+
}
|
|
54
|
+
/** Build the listFeatures Step from a named connection. */
|
|
55
|
+
declare function newListFeaturesStep<StepInput, OperationInput = ListFeaturesInput, OperationOutput = FeaturesPage>(configuration: ListFeaturesStepConfig<StepInput, OperationInput, OperationOutput>): QueryStep<StepInput, OperationInput, OperationOutput>;
|
|
56
|
+
|
|
57
|
+
/** Open one named Productboard connection from Dex project configuration. */
|
|
58
|
+
declare function newProjectConnection(project: LoadedProject, connectionName: string): Connection<Operations>;
|
|
59
|
+
|
|
60
|
+
/** Injectable provider sender for tests and local applications. */
|
|
61
|
+
interface ProductboardClientOptions {
|
|
62
|
+
/** Provider sender; omitted uses the SDK transport. */ send?: (request: ProviderRequest) => Promise<ProviderResponse>;
|
|
63
|
+
}
|
|
64
|
+
interface Exchange {
|
|
65
|
+
status: number;
|
|
66
|
+
body: Uint8Array;
|
|
67
|
+
retryAfterMs: number;
|
|
68
|
+
error?: 'credential' | 'request' | 'tooLarge';
|
|
69
|
+
}
|
|
70
|
+
/** Authenticated Productboard REST API v2 client for feature pages. */
|
|
71
|
+
declare class ProductboardClient {
|
|
72
|
+
private readonly credentials;
|
|
73
|
+
/** Validated provider base URL for requests and next-page links. */ readonly baseURL: string;
|
|
74
|
+
private readonly timeoutMs;
|
|
75
|
+
private readonly maxResponseBytes;
|
|
76
|
+
private readonly send;
|
|
77
|
+
/** Validate provider configuration without contacting Productboard. */
|
|
78
|
+
constructor(config: Config, credentials: CredentialProvider<Credentials>, options?: ProductboardClientOptions);
|
|
79
|
+
/** Bind the listFeatures Query. */ listFeatures(): ListFeaturesOperation;
|
|
80
|
+
/** Resolve the token and send one bounded feature-page request. */
|
|
81
|
+
get(call: Call, pageCursor: string): Promise<Exchange>;
|
|
82
|
+
}
|
|
83
|
+
/** Read one feature page with IDs, names, and the next opaque cursor. */
|
|
84
|
+
declare class ListFeaturesOperation implements Query<ListFeaturesInput, FeaturesPage> {
|
|
85
|
+
private readonly client;
|
|
86
|
+
/** Bind the provider client. */ constructor(client: ProductboardClient);
|
|
87
|
+
/** Return the generated Query contract. */ definition(): QueryDefinition;
|
|
88
|
+
/** Classify provider outcomes consistently with the Go implementation. */
|
|
89
|
+
invoke(call: Call, input: ListFeaturesInput): Promise<QueryAttempt<FeaturesPage>>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { Config, Connection, ConnectorID, Credentials, FeaturesPage, ListFeaturesBranch, ListFeaturesBranchDefect, ListFeaturesBranchInvalidResponse, ListFeaturesBranchListed, ListFeaturesBranchProviderRejected, ListFeaturesDefinition, ListFeaturesInput, ListFeaturesMetadata, ListFeaturesOperation, type ListFeaturesStepConfig, ProductboardClient, type ProductboardClientOptions, ProviderID, newListFeaturesStep, newProjectConnection };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConfigSchema,
|
|
3
|
+
Connection,
|
|
4
|
+
ConnectorID,
|
|
5
|
+
FeatureMetadataSchema,
|
|
6
|
+
FeaturesPageSchema,
|
|
7
|
+
ListFeaturesBranch,
|
|
8
|
+
ListFeaturesBranchDefect,
|
|
9
|
+
ListFeaturesBranchInvalidResponse,
|
|
10
|
+
ListFeaturesBranchListed,
|
|
11
|
+
ListFeaturesBranchProviderRejected,
|
|
12
|
+
ListFeaturesDefinition,
|
|
13
|
+
ListFeaturesInputSchema,
|
|
14
|
+
ListFeaturesMetadata,
|
|
15
|
+
ProviderID,
|
|
16
|
+
decodeCredentials,
|
|
17
|
+
newConnection,
|
|
18
|
+
newConnectionWithOperations,
|
|
19
|
+
newProjectConnection
|
|
20
|
+
} from "./chunk-GDHSBL3L.js";
|
|
21
|
+
|
|
22
|
+
// src/generated/steps.ts
|
|
23
|
+
import { goToBranch, newQueryStep } from "@superdurable/connectors-sdk";
|
|
24
|
+
function newListFeaturesStep(configuration) {
|
|
25
|
+
const { connection, connectionName, listed, providerRejected, invalidResponse, defect, ...stepConfiguration } = configuration;
|
|
26
|
+
if (!connection || connection.reference.name !== connectionName) throw new TypeError("Step connection name differs from the bound connection");
|
|
27
|
+
return newQueryStep({ ...stepConfiguration, operation: connection.operations.listFeatures(), connection: connection.reference, branches: [
|
|
28
|
+
goToBranch("listed", listed),
|
|
29
|
+
...providerRejected ? [goToBranch("providerRejected", providerRejected)] : [],
|
|
30
|
+
...invalidResponse ? [goToBranch("invalidResponse", invalidResponse)] : [],
|
|
31
|
+
...defect ? [goToBranch("defect", defect)] : []
|
|
32
|
+
] });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/project.ts
|
|
36
|
+
import { SecretString as SecretString2 } from "@superdurable/connectors-sdk";
|
|
37
|
+
import { z } from "zod";
|
|
38
|
+
|
|
39
|
+
// src/client.ts
|
|
40
|
+
import { SecretString, newQueryBranch, newQueryRetry } from "@superdurable/connectors-sdk";
|
|
41
|
+
import { BodyTooLargeError, isHeaderSafeCredential, parseRetryAfter, sendProviderRequest, validateBaseURL } from "@superdurable/connectors-sdk/providerhttp";
|
|
42
|
+
var defaultBaseURL = "https://api.productboard.com/v2";
|
|
43
|
+
var emptyPage = () => ({ features: [], nextCursor: "" });
|
|
44
|
+
var ProductboardClient = class {
|
|
45
|
+
/** Validate provider configuration without contacting Productboard. */
|
|
46
|
+
constructor(config, credentials, options = {}) {
|
|
47
|
+
this.credentials = credentials;
|
|
48
|
+
if (!config || !credentials) throw new TypeError("Productboard configuration and credentials are required");
|
|
49
|
+
this.baseURL = validateBaseURL(config.baseUrl || defaultBaseURL);
|
|
50
|
+
this.timeoutMs = config.timeoutMs || 15e3;
|
|
51
|
+
this.maxResponseBytes = config.maxResponseBytes || 1048576;
|
|
52
|
+
if (!Number.isSafeInteger(this.timeoutMs) || this.timeoutMs <= 0 || !Number.isSafeInteger(this.maxResponseBytes) || this.maxResponseBytes <= 0)
|
|
53
|
+
throw new TypeError("Productboard request limits must be positive integers");
|
|
54
|
+
this.send = options.send ?? sendProviderRequest;
|
|
55
|
+
}
|
|
56
|
+
credentials;
|
|
57
|
+
/** Validated provider base URL for requests and next-page links. */
|
|
58
|
+
baseURL;
|
|
59
|
+
timeoutMs;
|
|
60
|
+
maxResponseBytes;
|
|
61
|
+
send;
|
|
62
|
+
/** Bind the listFeatures Query. */
|
|
63
|
+
listFeatures() {
|
|
64
|
+
return new ListFeaturesOperation(this);
|
|
65
|
+
}
|
|
66
|
+
/** Resolve the token and send one bounded feature-page request. */
|
|
67
|
+
async get(call, pageCursor) {
|
|
68
|
+
let token;
|
|
69
|
+
try {
|
|
70
|
+
const credentials = await this.credentials.resolve(call, call.context.cancellationSignal);
|
|
71
|
+
token = credentials.apiToken instanceof SecretString ? credentials.apiToken.reveal() : "";
|
|
72
|
+
} catch {
|
|
73
|
+
return emptyExchange("credential");
|
|
74
|
+
}
|
|
75
|
+
if (!token || !isHeaderSafeCredential(token)) return emptyExchange("credential");
|
|
76
|
+
const url = new URL(`${this.baseURL}/entities`);
|
|
77
|
+
url.searchParams.set("fields[]", "name");
|
|
78
|
+
url.searchParams.set("type[]", "feature");
|
|
79
|
+
if (pageCursor) url.searchParams.set("pageCursor", pageCursor);
|
|
80
|
+
const request = {
|
|
81
|
+
url: url.toString(),
|
|
82
|
+
method: "GET",
|
|
83
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${token}` },
|
|
84
|
+
maxResponseBytes: this.maxResponseBytes,
|
|
85
|
+
timeoutMs: this.timeoutMs,
|
|
86
|
+
addressPolicy: this.baseURL.startsWith("https://") ? "publicHTTPS" : "privateNetworkHTTP",
|
|
87
|
+
signal: call.context.cancellationSignal
|
|
88
|
+
};
|
|
89
|
+
let response;
|
|
90
|
+
let isTooLarge = false;
|
|
91
|
+
try {
|
|
92
|
+
response = await this.send(request);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error instanceof BodyTooLargeError && error.status !== void 0) {
|
|
95
|
+
response = { status: error.status, headers: error.headers ?? {}, body: new Uint8Array() };
|
|
96
|
+
isTooLarge = true;
|
|
97
|
+
} else return emptyExchange("request");
|
|
98
|
+
}
|
|
99
|
+
const retryAfterMs = parseRetryAfter(header(response.headers, "retry-after"));
|
|
100
|
+
if (isTooLarge || response.body.length > this.maxResponseBytes)
|
|
101
|
+
return { status: response.status, body: new Uint8Array(), retryAfterMs, error: "tooLarge" };
|
|
102
|
+
return { status: response.status, body: response.body, retryAfterMs };
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var ListFeaturesOperation = class {
|
|
106
|
+
/** Bind the provider client. */
|
|
107
|
+
constructor(client) {
|
|
108
|
+
this.client = client;
|
|
109
|
+
}
|
|
110
|
+
client;
|
|
111
|
+
/** Return the generated Query contract. */
|
|
112
|
+
definition() {
|
|
113
|
+
return ListFeaturesDefinition;
|
|
114
|
+
}
|
|
115
|
+
/** Classify provider outcomes consistently with the Go implementation. */
|
|
116
|
+
async invoke(call, input) {
|
|
117
|
+
if (!isValidCursor(input?.pageCursor))
|
|
118
|
+
return terminal("defect", "VALIDATION", "page cursor contains invalid characters or exceeds 2048 bytes");
|
|
119
|
+
const exchange = await this.client.get(call, input.pageCursor);
|
|
120
|
+
if (exchange.error === "credential") return terminal("defect", "AUTHENTICATION", "Productboard credentials are unavailable or invalid");
|
|
121
|
+
if (exchange.error === "request") return retry("TRANSPORT", "Productboard request failed");
|
|
122
|
+
if (exchange.status === 429) return retry("RATE_LIMIT", "Productboard rate limited the request", exchange.retryAfterMs);
|
|
123
|
+
if (exchange.status === 408 || exchange.status >= 500) return retry("AVAILABILITY", "Productboard is temporarily unavailable", exchange.retryAfterMs);
|
|
124
|
+
if (exchange.status !== 200) return terminal("providerRejected", "PROVIDER_REJECTION", `Productboard rejected HTTP ${exchange.status}`);
|
|
125
|
+
if (exchange.error === "tooLarge") return terminal("invalidResponse", "RESPONSE_TOO_LARGE", "Productboard response exceeded the configured limit");
|
|
126
|
+
const page = decodePage(exchange.body, this.client.baseURL);
|
|
127
|
+
if (!page) return terminal("invalidResponse", "PROTOCOL", "Productboard returned invalid feature JSON");
|
|
128
|
+
return newQueryBranch("listed", page);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
function isValidCursor(value) {
|
|
132
|
+
if (typeof value !== "string" || Buffer.byteLength(value, "utf8") > 2048) return false;
|
|
133
|
+
for (const character of value) {
|
|
134
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
135
|
+
if (codePoint < 32 || codePoint >= 127 && codePoint <= 159) return false;
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
function emptyExchange(error) {
|
|
140
|
+
return { status: 0, body: new Uint8Array(), retryAfterMs: 0, error };
|
|
141
|
+
}
|
|
142
|
+
function failure(kind, message) {
|
|
143
|
+
return { provider: "productboard", operation: "listFeatures", kind, message };
|
|
144
|
+
}
|
|
145
|
+
function terminal(branch, kind, message) {
|
|
146
|
+
return newQueryBranch(branch, emptyPage(), {}, failure(kind, message));
|
|
147
|
+
}
|
|
148
|
+
function retry(kind, message, delay = 0) {
|
|
149
|
+
return newQueryRetry(failure(kind, message), delay);
|
|
150
|
+
}
|
|
151
|
+
function header(headers, name) {
|
|
152
|
+
const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
|
|
153
|
+
return Array.isArray(entry) ? entry[0] ?? "" : entry ?? "";
|
|
154
|
+
}
|
|
155
|
+
function isObject(value) {
|
|
156
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
157
|
+
}
|
|
158
|
+
function isNextPageURL(nextURL, baseURL) {
|
|
159
|
+
if (nextURL.protocol === "https:" && nextURL.host === "api.productboard.com" && nextURL.pathname === "/v2/entities") return true;
|
|
160
|
+
return `${nextURL.protocol}//${nextURL.host}${nextURL.pathname}` === `${baseURL.replace(/\/+$/, "")}/entities`;
|
|
161
|
+
}
|
|
162
|
+
function decodePage(body, baseURL) {
|
|
163
|
+
let raw;
|
|
164
|
+
try {
|
|
165
|
+
raw = JSON.parse(Buffer.from(body).toString("utf8"));
|
|
166
|
+
} catch {
|
|
167
|
+
return void 0;
|
|
168
|
+
}
|
|
169
|
+
if (!isObject(raw) || !Array.isArray(raw.data)) return void 0;
|
|
170
|
+
const features = [];
|
|
171
|
+
for (const entity of raw.data) {
|
|
172
|
+
if (!isObject(entity) || typeof entity.id !== "string" || !entity.id || entity.type !== "feature" || !isObject(entity.fields) || typeof entity.fields.name !== "string" || !entity.fields.name.trim()) return void 0;
|
|
173
|
+
features.push({ id: entity.id, name: entity.fields.name });
|
|
174
|
+
}
|
|
175
|
+
const links = raw.links === void 0 ? {} : raw.links;
|
|
176
|
+
if (!isObject(links) || links.next !== void 0 && typeof links.next !== "string") return void 0;
|
|
177
|
+
let nextCursor = "";
|
|
178
|
+
if (links.next) {
|
|
179
|
+
let nextURL;
|
|
180
|
+
try {
|
|
181
|
+
nextURL = new URL(links.next);
|
|
182
|
+
} catch {
|
|
183
|
+
return void 0;
|
|
184
|
+
}
|
|
185
|
+
if (!isNextPageURL(nextURL, baseURL)) return void 0;
|
|
186
|
+
const cursors = nextURL.searchParams.getAll("pageCursor");
|
|
187
|
+
if (cursors.length !== 1 || !cursors[0] || !isValidCursor(cursors[0])) return void 0;
|
|
188
|
+
nextCursor = cursors[0];
|
|
189
|
+
}
|
|
190
|
+
return { features, nextCursor };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/project.ts
|
|
194
|
+
function newProjectConnection2(project, connectionName) {
|
|
195
|
+
return newProjectConnection(project, connectionName, (loadedProject, name) => {
|
|
196
|
+
const selected = loadedProject.configuration.connections.find((entry) => entry.connectorId === "productboard" && entry.connectionName === name);
|
|
197
|
+
if (!selected) throw new TypeError("Productboard project connection is not configured");
|
|
198
|
+
const fields = z.strictObject({ baseUrl: z.string().optional(), timeout: z.union([z.string(), z.number().int()]).optional(), maxResponseBytes: z.number().int().optional() }).parse(selected.configuration);
|
|
199
|
+
const timeoutMs = typeof fields.timeout === "number" ? Math.ceil(fields.timeout / 1e6) : fields.timeout ? parseDurationMilliseconds(fields.timeout) : void 0;
|
|
200
|
+
const config = {
|
|
201
|
+
...fields.baseUrl ? { baseUrl: fields.baseUrl } : {},
|
|
202
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
203
|
+
...fields.maxResponseBytes !== void 0 ? { maxResponseBytes: fields.maxResponseBytes } : {}
|
|
204
|
+
};
|
|
205
|
+
const credentials = {
|
|
206
|
+
async resolve(_call, signal) {
|
|
207
|
+
const privateFields = await loadedProject.resolveCredential("productboard", name, signal);
|
|
208
|
+
if (typeof privateFields.api_token !== "string") throw new TypeError("Productboard credentials are unavailable");
|
|
209
|
+
return { apiToken: new SecretString2(privateFields.api_token) };
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
return new ProductboardClient(config, credentials);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function parseDurationMilliseconds(value) {
|
|
216
|
+
let remaining = value;
|
|
217
|
+
let milliseconds = 0;
|
|
218
|
+
const units = { ns: 1e-6, us: 1e-3, "\xB5s": 1e-3, ms: 1, s: 1e3, m: 6e4, h: 36e5 };
|
|
219
|
+
while (remaining) {
|
|
220
|
+
const part = /^(\d+(?:\.\d+)?)(ns|us|µs|ms|s|m|h)/.exec(remaining);
|
|
221
|
+
if (!part) throw new TypeError("Productboard timeout must be a positive Go duration");
|
|
222
|
+
milliseconds += Number(part[1]) * (units[part[2] ?? ""] ?? NaN);
|
|
223
|
+
remaining = remaining.slice(part[0].length);
|
|
224
|
+
}
|
|
225
|
+
if (!Number.isSafeInteger(milliseconds) || milliseconds <= 0) throw new TypeError("Productboard timeout must resolve to positive whole milliseconds");
|
|
226
|
+
return milliseconds;
|
|
227
|
+
}
|
|
228
|
+
export {
|
|
229
|
+
ConfigSchema,
|
|
230
|
+
Connection,
|
|
231
|
+
ConnectorID,
|
|
232
|
+
FeatureMetadataSchema,
|
|
233
|
+
FeaturesPageSchema,
|
|
234
|
+
ListFeaturesBranch,
|
|
235
|
+
ListFeaturesBranchDefect,
|
|
236
|
+
ListFeaturesBranchInvalidResponse,
|
|
237
|
+
ListFeaturesBranchListed,
|
|
238
|
+
ListFeaturesBranchProviderRejected,
|
|
239
|
+
ListFeaturesDefinition,
|
|
240
|
+
ListFeaturesInputSchema,
|
|
241
|
+
ListFeaturesMetadata,
|
|
242
|
+
ListFeaturesOperation,
|
|
243
|
+
ProductboardClient,
|
|
244
|
+
ProviderID,
|
|
245
|
+
decodeCredentials,
|
|
246
|
+
newConnection,
|
|
247
|
+
newConnectionWithOperations,
|
|
248
|
+
newListFeaturesStep,
|
|
249
|
+
newProjectConnection2 as newProjectConnection
|
|
250
|
+
};
|
package/dist/mock.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { MockQuery, Case } from '@superdurable/connectors-sdk/connectormock';
|
|
2
|
+
import { Failure } from '@superdurable/connectors-sdk';
|
|
3
|
+
import { O as Operations, C as Connection, L as ListFeaturesInput, F as FeaturesPage, e as FeatureMetadata } from './connection-CxSuhl7t.js';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
/** Go zero value for ListFeaturesInput in scripted cases. */
|
|
7
|
+
declare const emptyListFeaturesInput: ListFeaturesInput;
|
|
8
|
+
/** Go zero value for FeatureMetadata in scripted cases. */
|
|
9
|
+
declare const emptyFeatureMetadata: FeatureMetadata;
|
|
10
|
+
/** Go zero value for FeaturesPage in scripted cases. */
|
|
11
|
+
declare const emptyFeaturesPage: FeaturesPage;
|
|
12
|
+
/** Script the listed branch. */
|
|
13
|
+
declare function listFeaturesListed(value: FeaturesPage, failure?: Failure): Case<FeaturesPage>;
|
|
14
|
+
/** Script the providerRejected branch. */
|
|
15
|
+
declare function listFeaturesProviderRejected(value: FeaturesPage, failure?: Failure): Case<FeaturesPage>;
|
|
16
|
+
/** Script the invalidResponse branch. */
|
|
17
|
+
declare function listFeaturesInvalidResponse(value: FeaturesPage, failure?: Failure): Case<FeaturesPage>;
|
|
18
|
+
/** Script the defect branch. */
|
|
19
|
+
declare function listFeaturesDefect(value: FeaturesPage, failure?: Failure): Case<FeaturesPage>;
|
|
20
|
+
/** Script a safe retry of listFeatures. */
|
|
21
|
+
declare function listFeaturesRetry(failure: Failure, retryAfterMs?: number): Case<FeaturesPage>;
|
|
22
|
+
/** Manifest scenario listedDefault for listFeatures. */
|
|
23
|
+
declare function listFeaturesListedDefault(): Case<FeaturesPage>;
|
|
24
|
+
/** Default listFeatures scenario. */
|
|
25
|
+
declare function listFeaturesDefault(): Case<FeaturesPage>;
|
|
26
|
+
/** Scripted operations and manifest defaults for this connector. */
|
|
27
|
+
declare class GeneratedMock implements Operations {
|
|
28
|
+
#private;
|
|
29
|
+
constructor(connectionName: string);
|
|
30
|
+
connection(): Connection;
|
|
31
|
+
listFeatures(): MockQuery<ListFeaturesInput, FeaturesPage>;
|
|
32
|
+
assertNoFailures(): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { GeneratedMock, GeneratedMock as Mock, emptyFeatureMetadata, emptyFeaturesPage, emptyListFeaturesInput, listFeaturesDefault, listFeaturesDefect, listFeaturesInvalidResponse, listFeaturesListed, listFeaturesListedDefault, listFeaturesProviderRejected, listFeaturesRetry };
|
package/dist/mock.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FeatureMetadataSchema,
|
|
3
|
+
FeaturesPageSchema,
|
|
4
|
+
ListFeaturesDefinition,
|
|
5
|
+
ListFeaturesInputSchema,
|
|
6
|
+
ProviderID,
|
|
7
|
+
newConnectionWithOperations
|
|
8
|
+
} from "./chunk-GDHSBL3L.js";
|
|
9
|
+
|
|
10
|
+
// src/generated/mock.ts
|
|
11
|
+
import { branchCase, retryCase, newQuery } from "@superdurable/connectors-sdk/connectormock";
|
|
12
|
+
var emptyListFeaturesInput = ListFeaturesInputSchema.parse({
|
|
13
|
+
"pageCursor": ""
|
|
14
|
+
});
|
|
15
|
+
var emptyFeatureMetadata = FeatureMetadataSchema.parse({
|
|
16
|
+
"id": "",
|
|
17
|
+
"name": ""
|
|
18
|
+
});
|
|
19
|
+
var emptyFeaturesPage = FeaturesPageSchema.parse({
|
|
20
|
+
"features": [],
|
|
21
|
+
"nextCursor": ""
|
|
22
|
+
});
|
|
23
|
+
function listFeaturesListed(value, failure) {
|
|
24
|
+
return branchCase("listed", value, failure);
|
|
25
|
+
}
|
|
26
|
+
function listFeaturesProviderRejected(value, failure) {
|
|
27
|
+
return branchCase("providerRejected", value, failure);
|
|
28
|
+
}
|
|
29
|
+
function listFeaturesInvalidResponse(value, failure) {
|
|
30
|
+
return branchCase("invalidResponse", value, failure);
|
|
31
|
+
}
|
|
32
|
+
function listFeaturesDefect(value, failure) {
|
|
33
|
+
return branchCase("defect", value, failure);
|
|
34
|
+
}
|
|
35
|
+
function listFeaturesRetry(failure, retryAfterMs = 0) {
|
|
36
|
+
return retryCase(failure, retryAfterMs);
|
|
37
|
+
}
|
|
38
|
+
function listFeaturesListedDefault() {
|
|
39
|
+
const value = FeaturesPageSchema.parse({ "features": [{ "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Example feature" }], "nextCursor": "" });
|
|
40
|
+
return branchCase("listed", value);
|
|
41
|
+
}
|
|
42
|
+
function listFeaturesDefault() {
|
|
43
|
+
return listFeaturesListedDefault();
|
|
44
|
+
}
|
|
45
|
+
var GeneratedMock = class {
|
|
46
|
+
#listFeatures;
|
|
47
|
+
#connection;
|
|
48
|
+
constructor(connectionName) {
|
|
49
|
+
this.#listFeatures = newQuery(ListFeaturesDefinition).default(listFeaturesDefault());
|
|
50
|
+
this.#connection = newConnectionWithOperations(this, { provider: ProviderID, name: connectionName });
|
|
51
|
+
}
|
|
52
|
+
connection() {
|
|
53
|
+
return this.#connection;
|
|
54
|
+
}
|
|
55
|
+
listFeatures() {
|
|
56
|
+
return this.#listFeatures;
|
|
57
|
+
}
|
|
58
|
+
assertNoFailures() {
|
|
59
|
+
this.#listFeatures.assertNoFailures();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
export {
|
|
63
|
+
GeneratedMock,
|
|
64
|
+
GeneratedMock as Mock,
|
|
65
|
+
emptyFeatureMetadata,
|
|
66
|
+
emptyFeaturesPage,
|
|
67
|
+
emptyListFeaturesInput,
|
|
68
|
+
listFeaturesDefault,
|
|
69
|
+
listFeaturesDefect,
|
|
70
|
+
listFeaturesInvalidResponse,
|
|
71
|
+
listFeaturesListed,
|
|
72
|
+
listFeaturesListedDefault,
|
|
73
|
+
listFeaturesProviderRejected,
|
|
74
|
+
listFeaturesRetry
|
|
75
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superdurable/connector-productboard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Productboard connector for Dex TypeScript applications",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/superdurable/dex-connectors-library.git",
|
|
9
|
+
"directory": "connectors/productboard/typescript"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": "^22.0.0 || ^24.0.0"
|
|
14
|
+
},
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./mock": {
|
|
23
|
+
"types": "./dist/mock.d.ts",
|
|
24
|
+
"import": "./dist/mock.js"
|
|
25
|
+
},
|
|
26
|
+
"./dex-connector.json": "./dex-connector.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"dex-connector.json"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"prepack": "npm run build",
|
|
36
|
+
"build": "tsup src/index.ts src/mock.ts --format esm --dts --clean",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"lint": "eslint src test examples",
|
|
39
|
+
"test": "vitest run --exclude test/integration/**",
|
|
40
|
+
"test:integration": "vitest run test/integration/*.integration.test.ts",
|
|
41
|
+
"example:worker": "tsx examples/feature-discovery/worker.ts",
|
|
42
|
+
"example:start": "tsx examples/feature-discovery/start.ts",
|
|
43
|
+
"check": "npm run typecheck && npm run lint && npm test && npm run build"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@superdurable/connectors-sdk": "0.1.1",
|
|
47
|
+
"@superdurable/dex": ">=1.28.0",
|
|
48
|
+
"zod": "^4.1.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@eslint/js": "^9.0.0",
|
|
52
|
+
"@types/node": "^22.0.0",
|
|
53
|
+
"eslint": "^9.0.0",
|
|
54
|
+
"tsup": "^8.0.0",
|
|
55
|
+
"tsx": "^4.23.15",
|
|
56
|
+
"typescript": "^5.9.0",
|
|
57
|
+
"typescript-eslint": "^8.0.0",
|
|
58
|
+
"vitest": "^3.0.0",
|
|
59
|
+
"yaml": "^2.9.1"
|
|
60
|
+
}
|
|
61
|
+
}
|