@mitralab.io/functions-sdk 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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/contracts/sdk-core-v0.1.0.manifest.json +15 -0
- package/dist/index.cjs +328 -0
- package/dist/index.d.cts +50 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +302 -0
- package/package.json +87 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
## 0.1.0
|
|
6
|
+
|
|
7
|
+
- Add environment-based Server Function client configuration.
|
|
8
|
+
- Add current user, entity, custom query, Server Function, and integration APIs.
|
|
9
|
+
- Add typed errors, request timeout, credential redaction, and package release automation.
|
|
10
|
+
- Compose shared API contracts and modules from `@mitralab.io/sdk-core`.
|
|
11
|
+
- Add commit- and digest-pinned SDK parity gates backed by the installed Core corpus.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mitra Platform
|
|
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
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Mitra Functions SDK for JavaScript
|
|
2
|
+
|
|
3
|
+
JavaScript and TypeScript SDK for code running inside Mitra Server Functions. It exposes the application-scoped Platform APIs that a function commonly needs without bringing browser authentication into the runtime.
|
|
4
|
+
|
|
5
|
+
The SDK uses the short-lived platform access token injected into the function runtime. It does not log in users, persist credentials, refresh tokens, use API keys, or retry requests automatically.
|
|
6
|
+
|
|
7
|
+
Shared API contracts and modules come from `@mitralab.io/sdk-core`. This package owns only the Server Function runtime adapter: environment configuration, token headers, timeout, credential redaction, redirect refusal, and one-attempt request policy. The core never receives or stores the token.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @mitralab.io/functions-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Node.js 18 or newer is required.
|
|
16
|
+
|
|
17
|
+
## Runtime integration status
|
|
18
|
+
|
|
19
|
+
The SDK is ready for the target runtime contract, but Functions and Sandbox do not inject it yet. Until that integration is delivered, these variables must be supplied explicitly in local or controlled execution environments:
|
|
20
|
+
|
|
21
|
+
- `MITRA_API_URL`
|
|
22
|
+
- `MITRA_PLATFORM_ACCESS_TOKEN`
|
|
23
|
+
- `MITRA_APP_ID`
|
|
24
|
+
- `MITRA_DATA_SOURCE_ID`, when already resolved by the runtime
|
|
25
|
+
|
|
26
|
+
Do not treat user-defined function secrets as the runtime credential channel. The pending runtime integration must inject the platform access token separately from user secrets.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { createClient } from "@mitralab.io/functions-sdk"
|
|
32
|
+
|
|
33
|
+
const mitra = createClient()
|
|
34
|
+
|
|
35
|
+
export async function handler(input: { orderId: string }) {
|
|
36
|
+
const order = await mitra.entities.Order.get(input.orderId)
|
|
37
|
+
return { order }
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Once the runtime integration is available, `createClient()` reads the process environment by default. For local development or explicit dependency injection, pass configuration directly:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const mitra = createClient({
|
|
45
|
+
apiUrl: "https://api.example.com",
|
|
46
|
+
accessToken: process.env.MITRA_PLATFORM_ACCESS_TOKEN,
|
|
47
|
+
appId: "app-id",
|
|
48
|
+
dataSourceId: "data-source-id",
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`createClientFromEnvironment(env)` is available when a function needs to supply a specific environment object, such as in tests.
|
|
53
|
+
|
|
54
|
+
## Initialization
|
|
55
|
+
|
|
56
|
+
Entity operations use the app identity from the token and `X-App-Id`, so they are available immediately. Custom queries require a data source ID.
|
|
57
|
+
|
|
58
|
+
If `MITRA_DATA_SOURCE_ID` or `dataSourceId` is not configured, call `init()` once before executing queries. It resolves the data source from Code Studio and is safe to call concurrently.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const mitra = createClient()
|
|
62
|
+
await mitra.init()
|
|
63
|
+
|
|
64
|
+
const result = await mitra.queries.execute("5df41c69-2a74-4db6-9cca-4af2b473941f", {
|
|
65
|
+
status: "active",
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## API
|
|
70
|
+
|
|
71
|
+
### Current user
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const user = await mitra.auth.me()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The SDK only reads the current authenticated user. Login, logout, browser storage, and token refresh belong to the Platform SDK used by frontend applications.
|
|
78
|
+
|
|
79
|
+
### Entities
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
type Task = {
|
|
83
|
+
id: string
|
|
84
|
+
title: string
|
|
85
|
+
status: "pending" | "done"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const tasks = mitra.entities.getTable<Task>("Task")
|
|
89
|
+
const recent = await tasks.list({ sort: "-created_at", limit: 20 })
|
|
90
|
+
const pending = await tasks.filter({ status: "pending" })
|
|
91
|
+
const created = await tasks.create({ title: "Review order" })
|
|
92
|
+
await tasks.update(created.id, { status: "done" })
|
|
93
|
+
await tasks.delete(created.id)
|
|
94
|
+
|
|
95
|
+
const dynamic = await mitra.entities.Task.list()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Available methods are `list`, `filter`, `get`, `create`, `bulkCreate`, `update`, `delete`, and `deleteMany`.
|
|
99
|
+
|
|
100
|
+
### Custom queries
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
await mitra.init()
|
|
104
|
+
const result = await mitra.queries.execute("5df41c69-2a74-4db6-9cca-4af2b473941f", {
|
|
105
|
+
customerId: "customer-id",
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Server Functions
|
|
110
|
+
|
|
111
|
+
`execute` waits for a terminal execution response. `executeAsync` requests asynchronous execution and returns the pending execution record.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const completed = await mitra.functions.execute("function-id", { orderId: "order-id" })
|
|
115
|
+
const pending = await mitra.functions.executeAsync("function-id", { orderId: "order-id" })
|
|
116
|
+
const current = await mitra.functions.getExecution(pending.id)
|
|
117
|
+
await mitra.functions.cancelExecution(pending.id)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Integrations
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const resource = await mitra.integration.executeResource("resource-id", {
|
|
124
|
+
description: "Notebook",
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const proxy = await mitra.integration.execute("config-id", {
|
|
128
|
+
method: "GET",
|
|
129
|
+
endpoint: "/users",
|
|
130
|
+
queryParams: { limit: "10" },
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Credentials configured for an integration are injected by the Integration service. Do not pass provider credentials through function input.
|
|
135
|
+
|
|
136
|
+
## Errors and request behavior
|
|
137
|
+
|
|
138
|
+
API, timeout, invalid response, and network failures throw `MitraApiError`. The error exposes `status`, `code`, `details`, `requestId`, and `retryable`, preserving API metadata when it is provided. Configuration failures throw `MitraConfigurationError`.
|
|
139
|
+
|
|
140
|
+
When an API error omits `retryable`, the HTTP adapter classifies 4xx responses
|
|
141
|
+
as `false` and 5xx responses as `true`. This field is diagnostic classification
|
|
142
|
+
only. The SDK still makes one attempt and never retries a request automatically.
|
|
143
|
+
|
|
144
|
+
The Python package uses idiomatic specialized names for local failures. JavaScript `REQUEST_TIMEOUT` and `NETWORK_ERROR` map to Python `MitraNetworkError`, while JavaScript `INVALID_RESPONSE` maps to Python `MitraResponseError`. API responses use `MitraApiError` in both packages.
|
|
145
|
+
|
|
146
|
+
Every request sends `Authorization: Bearer <token>` and `X-App-Id`. The default timeout is 10 seconds. Requests are never retried automatically, which avoids replaying writes with unknown idempotency.
|
|
147
|
+
|
|
148
|
+
The access token is redacted from API error messages and error details.
|
|
149
|
+
|
|
150
|
+
## Contract parity
|
|
151
|
+
|
|
152
|
+
The test suite reads SDK-PARITY-001 directly from the installed
|
|
153
|
+
`@mitralab.io/sdk-core` package. The source manifest pins Core version 0.1.0,
|
|
154
|
+
the SHA-256 digest, and the full source commit
|
|
155
|
+
`b513454d0d1f7344a4656cd9c0e1e32530c5ea90`. It does not duplicate the corpus.
|
|
156
|
+
|
|
157
|
+
The JavaScript consumer requirement is exactly `httpAdapterCases: all`. Tests
|
|
158
|
+
execute all recorded 404, 503, and timeout cases through the public client and
|
|
159
|
+
the real `HttpClient`, using a mock fetch only at the network boundary. They
|
|
160
|
+
verify the request and the complete error semantics. Continuous integration and
|
|
161
|
+
release gates also download the canonical file from the public Core repository
|
|
162
|
+
at the pinned commit and compare it byte for byte with the installed package.
|
|
163
|
+
|
|
164
|
+
## Scope
|
|
165
|
+
|
|
166
|
+
Version 0.1 includes application runtime operations: current user, entity CRUD, custom query execution, Server Function execution management, and integrations. Administrative control-plane methods and the legacy messenger API are intentionally excluded.
|
|
167
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm install
|
|
172
|
+
npm run check
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`@mitralab.io/sdk-core@0.1.0` is resolved from the public npm registry and
|
|
176
|
+
locked by integrity in `package-lock.json`. Do not replace it with a `file:`
|
|
177
|
+
dependency or a local tarball.
|
|
178
|
+
|
|
179
|
+
The contract source can be checked explicitly without changing the manifest or
|
|
180
|
+
lockfile:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
npm run check:contracts -- --canonical /path/to/sdk-parity.json
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The package builds ESM, CommonJS, and TypeScript declarations in `dist/`. Tests enforce at least 80 percent coverage for lines, functions, branches, and statements.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contract": "SDK-PARITY-001",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"dependency": {
|
|
5
|
+
"package": "@mitralab.io/sdk-core",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"contractPath": "contracts/v0.1.0/sdk-parity.json"
|
|
8
|
+
},
|
|
9
|
+
"source": {
|
|
10
|
+
"repository": "https://github.com/mitralab-dev/mitra-core-sdk",
|
|
11
|
+
"commit": "b513454d0d1f7344a4656cd9c0e1e32530c5ea90",
|
|
12
|
+
"path": "contracts/v0.1.0/sdk-parity.json"
|
|
13
|
+
},
|
|
14
|
+
"sha256": "600be3688cea7cfef16a8cf347516e87949b8e4151efb2c4136a2345557dde70"
|
|
15
|
+
}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
MitraApiError: () => MitraApiError,
|
|
24
|
+
MitraConfigurationError: () => MitraConfigurationError,
|
|
25
|
+
createClient: () => createClient,
|
|
26
|
+
createClientFromEnvironment: () => createClientFromEnvironment
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/client.ts
|
|
31
|
+
var import_sdk_core = require("@mitralab.io/sdk-core");
|
|
32
|
+
|
|
33
|
+
// src/errors.ts
|
|
34
|
+
var MitraConfigurationError = class extends Error {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "MitraConfigurationError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var MitraApiError = class extends Error {
|
|
41
|
+
status;
|
|
42
|
+
code;
|
|
43
|
+
details;
|
|
44
|
+
requestId;
|
|
45
|
+
retryable;
|
|
46
|
+
constructor(message, status, options = {}) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "MitraApiError";
|
|
49
|
+
this.status = status;
|
|
50
|
+
this.code = options.code;
|
|
51
|
+
this.details = options.details;
|
|
52
|
+
this.requestId = options.requestId;
|
|
53
|
+
this.retryable = options.retryable;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/config.ts
|
|
58
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
59
|
+
function readDefaultEnvironment() {
|
|
60
|
+
return typeof process === "undefined" ? {} : process.env;
|
|
61
|
+
}
|
|
62
|
+
function requiredValue(value, name) {
|
|
63
|
+
if (!value?.trim()) {
|
|
64
|
+
throw new MitraConfigurationError(`${name} is required`);
|
|
65
|
+
}
|
|
66
|
+
return value.trim();
|
|
67
|
+
}
|
|
68
|
+
function normalizeApiUrl(value) {
|
|
69
|
+
let parsed;
|
|
70
|
+
try {
|
|
71
|
+
parsed = new URL(value);
|
|
72
|
+
} catch {
|
|
73
|
+
throw new MitraConfigurationError("apiUrl must be a valid HTTP or HTTPS URL");
|
|
74
|
+
}
|
|
75
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
76
|
+
throw new MitraConfigurationError("apiUrl must be a valid HTTP or HTTPS URL");
|
|
77
|
+
}
|
|
78
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash || value.includes("?") || value.includes("#")) {
|
|
79
|
+
throw new MitraConfigurationError(
|
|
80
|
+
"apiUrl must not include credentials, query parameters, or a fragment"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return parsed.toString().replace(/\/+$/, "");
|
|
84
|
+
}
|
|
85
|
+
function resolveConfig(config = {}, environment = readDefaultEnvironment()) {
|
|
86
|
+
const apiUrl = normalizeApiUrl(
|
|
87
|
+
requiredValue(config.apiUrl ?? environment.MITRA_API_URL, "apiUrl")
|
|
88
|
+
);
|
|
89
|
+
const accessToken = requiredValue(
|
|
90
|
+
config.accessToken ?? environment.MITRA_PLATFORM_ACCESS_TOKEN,
|
|
91
|
+
"accessToken"
|
|
92
|
+
);
|
|
93
|
+
const appId = requiredValue(config.appId ?? environment.MITRA_APP_ID, "appId");
|
|
94
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
95
|
+
const fetchImplementation = config.fetch ?? globalThis.fetch;
|
|
96
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
97
|
+
throw new MitraConfigurationError("timeoutMs must be a positive number");
|
|
98
|
+
}
|
|
99
|
+
if (typeof fetchImplementation !== "function") {
|
|
100
|
+
throw new MitraConfigurationError("A fetch implementation is required");
|
|
101
|
+
}
|
|
102
|
+
const dataSourceId = config.dataSourceId ?? environment.MITRA_DATA_SOURCE_ID;
|
|
103
|
+
if (dataSourceId !== void 0 && !dataSourceId.trim()) {
|
|
104
|
+
throw new MitraConfigurationError("dataSourceId must not be empty when provided");
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
apiUrl,
|
|
108
|
+
accessToken,
|
|
109
|
+
appId,
|
|
110
|
+
...dataSourceId === void 0 ? {} : { dataSourceId: dataSourceId.trim() },
|
|
111
|
+
timeoutMs,
|
|
112
|
+
fetch: fetchImplementation
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/http-client.ts
|
|
117
|
+
var bearerCredentialPattern = /(\bBearer\s+)\S+/gi;
|
|
118
|
+
function redactText(value, accessToken) {
|
|
119
|
+
return value.replace(bearerCredentialPattern, "$1[REDACTED]").split(accessToken).join("[REDACTED]");
|
|
120
|
+
}
|
|
121
|
+
function redactDetails(value, accessToken) {
|
|
122
|
+
if (typeof value === "string") return redactText(value, accessToken);
|
|
123
|
+
if (Array.isArray(value)) return value.map((item) => redactDetails(item, accessToken));
|
|
124
|
+
if (value && typeof value === "object") {
|
|
125
|
+
return Object.fromEntries(
|
|
126
|
+
Object.entries(value).map(([key, entry]) => {
|
|
127
|
+
const sensitive = /token|authorization|password|secret|api.?key/i.test(key);
|
|
128
|
+
return [
|
|
129
|
+
redactText(key, accessToken),
|
|
130
|
+
sensitive ? "[REDACTED]" : redactDetails(entry, accessToken)
|
|
131
|
+
];
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
function optionalString(value) {
|
|
138
|
+
return typeof value === "string" ? value : void 0;
|
|
139
|
+
}
|
|
140
|
+
var HttpClient = class {
|
|
141
|
+
#accessToken;
|
|
142
|
+
baseUrl;
|
|
143
|
+
appId;
|
|
144
|
+
timeoutMs;
|
|
145
|
+
fetchImplementation;
|
|
146
|
+
constructor(config) {
|
|
147
|
+
this.baseUrl = config.baseUrl;
|
|
148
|
+
this.#accessToken = config.accessToken;
|
|
149
|
+
this.appId = config.appId;
|
|
150
|
+
this.timeoutMs = config.timeoutMs;
|
|
151
|
+
this.fetchImplementation = config.fetch;
|
|
152
|
+
}
|
|
153
|
+
async request(path, options = {}) {
|
|
154
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
155
|
+
for (const [key, value] of Object.entries(options.params ?? {})) {
|
|
156
|
+
if (value !== void 0) url.searchParams.append(key, String(value));
|
|
157
|
+
}
|
|
158
|
+
const controller = new AbortController();
|
|
159
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
160
|
+
const hasBody = options.body !== void 0;
|
|
161
|
+
try {
|
|
162
|
+
const response = await this.fetchImplementation(url, {
|
|
163
|
+
method: options.method ?? "GET",
|
|
164
|
+
headers: {
|
|
165
|
+
Accept: "application/json",
|
|
166
|
+
...hasBody ? { "Content-Type": "application/json" } : {},
|
|
167
|
+
Authorization: `Bearer ${this.#accessToken}`,
|
|
168
|
+
"X-App-Id": this.appId,
|
|
169
|
+
...options.headers
|
|
170
|
+
},
|
|
171
|
+
...hasBody ? { body: JSON.stringify(options.body) } : {},
|
|
172
|
+
redirect: "manual",
|
|
173
|
+
signal: controller.signal
|
|
174
|
+
});
|
|
175
|
+
if (response.status === 204) return void 0;
|
|
176
|
+
const text = await response.text();
|
|
177
|
+
let payload;
|
|
178
|
+
try {
|
|
179
|
+
payload = JSON.parse(text);
|
|
180
|
+
} catch {
|
|
181
|
+
if (response.ok) {
|
|
182
|
+
throw new MitraApiError("The Mitra API returned invalid JSON", response.status, {
|
|
183
|
+
code: "INVALID_RESPONSE",
|
|
184
|
+
retryable: false
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
payload = {};
|
|
188
|
+
}
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
const errorPayload = payload && typeof payload === "object" ? payload : {};
|
|
191
|
+
const rawMessage = optionalString(errorPayload.message);
|
|
192
|
+
const message = redactText(
|
|
193
|
+
rawMessage ?? `Request failed with status ${response.status}`,
|
|
194
|
+
this.#accessToken
|
|
195
|
+
);
|
|
196
|
+
const rawRequestId = optionalString(errorPayload.request_id) ?? optionalString(errorPayload.requestId) ?? response.headers.get("X-Request-Id") ?? void 0;
|
|
197
|
+
const rawCode = optionalString(errorPayload.error_code) ?? optionalString(errorPayload.code);
|
|
198
|
+
const requestId = rawRequestId === void 0 ? void 0 : redactText(rawRequestId, this.#accessToken);
|
|
199
|
+
const code = rawCode === void 0 ? void 0 : redactText(rawCode, this.#accessToken);
|
|
200
|
+
const retryable = typeof errorPayload.retryable === "boolean" ? errorPayload.retryable : response.status >= 500;
|
|
201
|
+
throw new MitraApiError(message, response.status, {
|
|
202
|
+
...code === void 0 ? {} : { code },
|
|
203
|
+
...errorPayload.details === void 0 ? {} : { details: redactDetails(errorPayload.details, this.#accessToken) },
|
|
204
|
+
...requestId === void 0 ? {} : { requestId },
|
|
205
|
+
...retryable === void 0 ? {} : { retryable }
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return payload;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error instanceof MitraApiError) throw error;
|
|
211
|
+
if (controller.signal.aborted) {
|
|
212
|
+
throw new MitraApiError("Mitra request timed out", 0, {
|
|
213
|
+
code: "REQUEST_TIMEOUT",
|
|
214
|
+
retryable: true
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
throw new MitraApiError("The Mitra API request failed", 0, {
|
|
218
|
+
code: "NETWORK_ERROR",
|
|
219
|
+
retryable: true
|
|
220
|
+
});
|
|
221
|
+
} finally {
|
|
222
|
+
clearTimeout(timeout);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
get(path, params) {
|
|
226
|
+
return this.request(path, { method: "GET", ...params === void 0 ? {} : { params } });
|
|
227
|
+
}
|
|
228
|
+
post(path, body, headers) {
|
|
229
|
+
return this.request(path, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
...body === void 0 ? {} : { body },
|
|
232
|
+
...headers === void 0 ? {} : { headers }
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
put(path, body) {
|
|
236
|
+
return this.request(path, { method: "PUT", body });
|
|
237
|
+
}
|
|
238
|
+
delete(path, params) {
|
|
239
|
+
return this.request(path, {
|
|
240
|
+
method: "DELETE",
|
|
241
|
+
...params === void 0 ? {} : { params }
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// src/client.ts
|
|
247
|
+
var coreErrors = {
|
|
248
|
+
configuration: (message) => new MitraConfigurationError(message),
|
|
249
|
+
invalidResponse: (message) => new MitraApiError(message, 200, { code: "INVALID_RESPONSE", retryable: false })
|
|
250
|
+
};
|
|
251
|
+
var DefaultMitraClient = class {
|
|
252
|
+
auth;
|
|
253
|
+
entities;
|
|
254
|
+
functions;
|
|
255
|
+
integration;
|
|
256
|
+
queries;
|
|
257
|
+
#dataSourceId;
|
|
258
|
+
#initPromise;
|
|
259
|
+
#appId;
|
|
260
|
+
#codeStudioHttpClient;
|
|
261
|
+
constructor(config) {
|
|
262
|
+
this.#appId = config.appId;
|
|
263
|
+
this.#dataSourceId = config.dataSourceId;
|
|
264
|
+
const httpClient = (service) => new HttpClient({
|
|
265
|
+
baseUrl: `${config.apiUrl}/${service}`,
|
|
266
|
+
accessToken: config.accessToken,
|
|
267
|
+
appId: config.appId,
|
|
268
|
+
timeoutMs: config.timeoutMs,
|
|
269
|
+
fetch: config.fetch
|
|
270
|
+
});
|
|
271
|
+
const core = (0, import_sdk_core.createSdkCore)({
|
|
272
|
+
transports: {
|
|
273
|
+
auth: httpClient("iam"),
|
|
274
|
+
dataManager: httpClient("data-manager"),
|
|
275
|
+
functions: httpClient("functions"),
|
|
276
|
+
integration: httpClient("integration")
|
|
277
|
+
},
|
|
278
|
+
getDataSourceId: () => this.#dataSourceId,
|
|
279
|
+
functions: { executeInvocationType: "sync", emptyInput: "empty-object" },
|
|
280
|
+
errors: coreErrors
|
|
281
|
+
});
|
|
282
|
+
this.auth = core.auth;
|
|
283
|
+
this.entities = core.entities;
|
|
284
|
+
this.functions = core.functions;
|
|
285
|
+
this.integration = core.integration;
|
|
286
|
+
this.queries = core.queries;
|
|
287
|
+
this.#codeStudioHttpClient = httpClient("code-studio");
|
|
288
|
+
}
|
|
289
|
+
init() {
|
|
290
|
+
if (this.#dataSourceId) return Promise.resolve();
|
|
291
|
+
if (!this.#initPromise) {
|
|
292
|
+
this.#initPromise = this.resolveDataSourceId().catch((error) => {
|
|
293
|
+
this.#initPromise = void 0;
|
|
294
|
+
throw error;
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return this.#initPromise;
|
|
298
|
+
}
|
|
299
|
+
async resolveDataSourceId() {
|
|
300
|
+
const appInfo = (0, import_sdk_core.expectObject)(
|
|
301
|
+
await this.#codeStudioHttpClient.get(
|
|
302
|
+
`/api/v1/apps/${(0, import_sdk_core.encodePathSegment)(this.#appId, "appId", coreErrors)}/info`
|
|
303
|
+
),
|
|
304
|
+
"App info response",
|
|
305
|
+
coreErrors
|
|
306
|
+
);
|
|
307
|
+
if (!appInfo.dataSourceId?.trim()) {
|
|
308
|
+
throw new MitraApiError("The app info response does not include a dataSourceId", 200, {
|
|
309
|
+
code: "INVALID_RESPONSE",
|
|
310
|
+
retryable: false
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
this.#dataSourceId = appInfo.dataSourceId;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
function createClient(config = {}) {
|
|
317
|
+
return new DefaultMitraClient(resolveConfig(config));
|
|
318
|
+
}
|
|
319
|
+
function createClientFromEnvironment(environment) {
|
|
320
|
+
return new DefaultMitraClient(resolveConfig({}, environment));
|
|
321
|
+
}
|
|
322
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
323
|
+
0 && (module.exports = {
|
|
324
|
+
MitraApiError,
|
|
325
|
+
MitraConfigurationError,
|
|
326
|
+
createClient,
|
|
327
|
+
createClientFromEnvironment
|
|
328
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AuthModule, EntitiesProxy, FunctionsModule, IntegrationModule, QueriesModule } from '@mitralab.io/sdk-core';
|
|
2
|
+
export { EntityListOptions, EntityTable, FunctionExecution, Plan, ProxyInput, ProxyResult, QueryResult, Tenant, User } from '@mitralab.io/sdk-core';
|
|
3
|
+
|
|
4
|
+
type Fetch = typeof globalThis.fetch;
|
|
5
|
+
interface MitraEnvironment {
|
|
6
|
+
MITRA_API_URL?: string;
|
|
7
|
+
MITRA_PLATFORM_ACCESS_TOKEN?: string;
|
|
8
|
+
MITRA_APP_ID?: string;
|
|
9
|
+
MITRA_DATA_SOURCE_ID?: string;
|
|
10
|
+
[key: string]: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
interface MitraClientConfig {
|
|
13
|
+
apiUrl?: string;
|
|
14
|
+
accessToken?: string;
|
|
15
|
+
appId?: string;
|
|
16
|
+
dataSourceId?: string;
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
fetch?: Fetch;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface MitraClient {
|
|
22
|
+
init(): Promise<void>;
|
|
23
|
+
readonly auth: AuthModule;
|
|
24
|
+
readonly entities: EntitiesProxy;
|
|
25
|
+
readonly functions: FunctionsModule;
|
|
26
|
+
readonly integration: IntegrationModule;
|
|
27
|
+
readonly queries: QueriesModule;
|
|
28
|
+
}
|
|
29
|
+
declare function createClient(config?: MitraClientConfig): MitraClient;
|
|
30
|
+
declare function createClientFromEnvironment(environment?: MitraEnvironment): MitraClient;
|
|
31
|
+
|
|
32
|
+
declare class MitraConfigurationError extends Error {
|
|
33
|
+
constructor(message: string);
|
|
34
|
+
}
|
|
35
|
+
interface MitraApiErrorOptions {
|
|
36
|
+
code?: string;
|
|
37
|
+
details?: unknown;
|
|
38
|
+
requestId?: string;
|
|
39
|
+
retryable?: boolean;
|
|
40
|
+
}
|
|
41
|
+
declare class MitraApiError extends Error {
|
|
42
|
+
readonly status: number;
|
|
43
|
+
readonly code: string | undefined;
|
|
44
|
+
readonly details: unknown;
|
|
45
|
+
readonly requestId: string | undefined;
|
|
46
|
+
readonly retryable: boolean | undefined;
|
|
47
|
+
constructor(message: string, status: number, options?: MitraApiErrorOptions);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { type Fetch, MitraApiError, type MitraClient, type MitraClientConfig, MitraConfigurationError, type MitraEnvironment, createClient, createClientFromEnvironment };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AuthModule, EntitiesProxy, FunctionsModule, IntegrationModule, QueriesModule } from '@mitralab.io/sdk-core';
|
|
2
|
+
export { EntityListOptions, EntityTable, FunctionExecution, Plan, ProxyInput, ProxyResult, QueryResult, Tenant, User } from '@mitralab.io/sdk-core';
|
|
3
|
+
|
|
4
|
+
type Fetch = typeof globalThis.fetch;
|
|
5
|
+
interface MitraEnvironment {
|
|
6
|
+
MITRA_API_URL?: string;
|
|
7
|
+
MITRA_PLATFORM_ACCESS_TOKEN?: string;
|
|
8
|
+
MITRA_APP_ID?: string;
|
|
9
|
+
MITRA_DATA_SOURCE_ID?: string;
|
|
10
|
+
[key: string]: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
interface MitraClientConfig {
|
|
13
|
+
apiUrl?: string;
|
|
14
|
+
accessToken?: string;
|
|
15
|
+
appId?: string;
|
|
16
|
+
dataSourceId?: string;
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
fetch?: Fetch;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface MitraClient {
|
|
22
|
+
init(): Promise<void>;
|
|
23
|
+
readonly auth: AuthModule;
|
|
24
|
+
readonly entities: EntitiesProxy;
|
|
25
|
+
readonly functions: FunctionsModule;
|
|
26
|
+
readonly integration: IntegrationModule;
|
|
27
|
+
readonly queries: QueriesModule;
|
|
28
|
+
}
|
|
29
|
+
declare function createClient(config?: MitraClientConfig): MitraClient;
|
|
30
|
+
declare function createClientFromEnvironment(environment?: MitraEnvironment): MitraClient;
|
|
31
|
+
|
|
32
|
+
declare class MitraConfigurationError extends Error {
|
|
33
|
+
constructor(message: string);
|
|
34
|
+
}
|
|
35
|
+
interface MitraApiErrorOptions {
|
|
36
|
+
code?: string;
|
|
37
|
+
details?: unknown;
|
|
38
|
+
requestId?: string;
|
|
39
|
+
retryable?: boolean;
|
|
40
|
+
}
|
|
41
|
+
declare class MitraApiError extends Error {
|
|
42
|
+
readonly status: number;
|
|
43
|
+
readonly code: string | undefined;
|
|
44
|
+
readonly details: unknown;
|
|
45
|
+
readonly requestId: string | undefined;
|
|
46
|
+
readonly retryable: boolean | undefined;
|
|
47
|
+
constructor(message: string, status: number, options?: MitraApiErrorOptions);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { type Fetch, MitraApiError, type MitraClient, type MitraClientConfig, MitraConfigurationError, type MitraEnvironment, createClient, createClientFromEnvironment };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import {
|
|
3
|
+
createSdkCore,
|
|
4
|
+
encodePathSegment,
|
|
5
|
+
expectObject
|
|
6
|
+
} from "@mitralab.io/sdk-core";
|
|
7
|
+
|
|
8
|
+
// src/errors.ts
|
|
9
|
+
var MitraConfigurationError = class extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "MitraConfigurationError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var MitraApiError = class extends Error {
|
|
16
|
+
status;
|
|
17
|
+
code;
|
|
18
|
+
details;
|
|
19
|
+
requestId;
|
|
20
|
+
retryable;
|
|
21
|
+
constructor(message, status, options = {}) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "MitraApiError";
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.code = options.code;
|
|
26
|
+
this.details = options.details;
|
|
27
|
+
this.requestId = options.requestId;
|
|
28
|
+
this.retryable = options.retryable;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/config.ts
|
|
33
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
34
|
+
function readDefaultEnvironment() {
|
|
35
|
+
return typeof process === "undefined" ? {} : process.env;
|
|
36
|
+
}
|
|
37
|
+
function requiredValue(value, name) {
|
|
38
|
+
if (!value?.trim()) {
|
|
39
|
+
throw new MitraConfigurationError(`${name} is required`);
|
|
40
|
+
}
|
|
41
|
+
return value.trim();
|
|
42
|
+
}
|
|
43
|
+
function normalizeApiUrl(value) {
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = new URL(value);
|
|
47
|
+
} catch {
|
|
48
|
+
throw new MitraConfigurationError("apiUrl must be a valid HTTP or HTTPS URL");
|
|
49
|
+
}
|
|
50
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
51
|
+
throw new MitraConfigurationError("apiUrl must be a valid HTTP or HTTPS URL");
|
|
52
|
+
}
|
|
53
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash || value.includes("?") || value.includes("#")) {
|
|
54
|
+
throw new MitraConfigurationError(
|
|
55
|
+
"apiUrl must not include credentials, query parameters, or a fragment"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return parsed.toString().replace(/\/+$/, "");
|
|
59
|
+
}
|
|
60
|
+
function resolveConfig(config = {}, environment = readDefaultEnvironment()) {
|
|
61
|
+
const apiUrl = normalizeApiUrl(
|
|
62
|
+
requiredValue(config.apiUrl ?? environment.MITRA_API_URL, "apiUrl")
|
|
63
|
+
);
|
|
64
|
+
const accessToken = requiredValue(
|
|
65
|
+
config.accessToken ?? environment.MITRA_PLATFORM_ACCESS_TOKEN,
|
|
66
|
+
"accessToken"
|
|
67
|
+
);
|
|
68
|
+
const appId = requiredValue(config.appId ?? environment.MITRA_APP_ID, "appId");
|
|
69
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
70
|
+
const fetchImplementation = config.fetch ?? globalThis.fetch;
|
|
71
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
72
|
+
throw new MitraConfigurationError("timeoutMs must be a positive number");
|
|
73
|
+
}
|
|
74
|
+
if (typeof fetchImplementation !== "function") {
|
|
75
|
+
throw new MitraConfigurationError("A fetch implementation is required");
|
|
76
|
+
}
|
|
77
|
+
const dataSourceId = config.dataSourceId ?? environment.MITRA_DATA_SOURCE_ID;
|
|
78
|
+
if (dataSourceId !== void 0 && !dataSourceId.trim()) {
|
|
79
|
+
throw new MitraConfigurationError("dataSourceId must not be empty when provided");
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
apiUrl,
|
|
83
|
+
accessToken,
|
|
84
|
+
appId,
|
|
85
|
+
...dataSourceId === void 0 ? {} : { dataSourceId: dataSourceId.trim() },
|
|
86
|
+
timeoutMs,
|
|
87
|
+
fetch: fetchImplementation
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/http-client.ts
|
|
92
|
+
var bearerCredentialPattern = /(\bBearer\s+)\S+/gi;
|
|
93
|
+
function redactText(value, accessToken) {
|
|
94
|
+
return value.replace(bearerCredentialPattern, "$1[REDACTED]").split(accessToken).join("[REDACTED]");
|
|
95
|
+
}
|
|
96
|
+
function redactDetails(value, accessToken) {
|
|
97
|
+
if (typeof value === "string") return redactText(value, accessToken);
|
|
98
|
+
if (Array.isArray(value)) return value.map((item) => redactDetails(item, accessToken));
|
|
99
|
+
if (value && typeof value === "object") {
|
|
100
|
+
return Object.fromEntries(
|
|
101
|
+
Object.entries(value).map(([key, entry]) => {
|
|
102
|
+
const sensitive = /token|authorization|password|secret|api.?key/i.test(key);
|
|
103
|
+
return [
|
|
104
|
+
redactText(key, accessToken),
|
|
105
|
+
sensitive ? "[REDACTED]" : redactDetails(entry, accessToken)
|
|
106
|
+
];
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
function optionalString(value) {
|
|
113
|
+
return typeof value === "string" ? value : void 0;
|
|
114
|
+
}
|
|
115
|
+
var HttpClient = class {
|
|
116
|
+
#accessToken;
|
|
117
|
+
baseUrl;
|
|
118
|
+
appId;
|
|
119
|
+
timeoutMs;
|
|
120
|
+
fetchImplementation;
|
|
121
|
+
constructor(config) {
|
|
122
|
+
this.baseUrl = config.baseUrl;
|
|
123
|
+
this.#accessToken = config.accessToken;
|
|
124
|
+
this.appId = config.appId;
|
|
125
|
+
this.timeoutMs = config.timeoutMs;
|
|
126
|
+
this.fetchImplementation = config.fetch;
|
|
127
|
+
}
|
|
128
|
+
async request(path, options = {}) {
|
|
129
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
130
|
+
for (const [key, value] of Object.entries(options.params ?? {})) {
|
|
131
|
+
if (value !== void 0) url.searchParams.append(key, String(value));
|
|
132
|
+
}
|
|
133
|
+
const controller = new AbortController();
|
|
134
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
135
|
+
const hasBody = options.body !== void 0;
|
|
136
|
+
try {
|
|
137
|
+
const response = await this.fetchImplementation(url, {
|
|
138
|
+
method: options.method ?? "GET",
|
|
139
|
+
headers: {
|
|
140
|
+
Accept: "application/json",
|
|
141
|
+
...hasBody ? { "Content-Type": "application/json" } : {},
|
|
142
|
+
Authorization: `Bearer ${this.#accessToken}`,
|
|
143
|
+
"X-App-Id": this.appId,
|
|
144
|
+
...options.headers
|
|
145
|
+
},
|
|
146
|
+
...hasBody ? { body: JSON.stringify(options.body) } : {},
|
|
147
|
+
redirect: "manual",
|
|
148
|
+
signal: controller.signal
|
|
149
|
+
});
|
|
150
|
+
if (response.status === 204) return void 0;
|
|
151
|
+
const text = await response.text();
|
|
152
|
+
let payload;
|
|
153
|
+
try {
|
|
154
|
+
payload = JSON.parse(text);
|
|
155
|
+
} catch {
|
|
156
|
+
if (response.ok) {
|
|
157
|
+
throw new MitraApiError("The Mitra API returned invalid JSON", response.status, {
|
|
158
|
+
code: "INVALID_RESPONSE",
|
|
159
|
+
retryable: false
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
payload = {};
|
|
163
|
+
}
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const errorPayload = payload && typeof payload === "object" ? payload : {};
|
|
166
|
+
const rawMessage = optionalString(errorPayload.message);
|
|
167
|
+
const message = redactText(
|
|
168
|
+
rawMessage ?? `Request failed with status ${response.status}`,
|
|
169
|
+
this.#accessToken
|
|
170
|
+
);
|
|
171
|
+
const rawRequestId = optionalString(errorPayload.request_id) ?? optionalString(errorPayload.requestId) ?? response.headers.get("X-Request-Id") ?? void 0;
|
|
172
|
+
const rawCode = optionalString(errorPayload.error_code) ?? optionalString(errorPayload.code);
|
|
173
|
+
const requestId = rawRequestId === void 0 ? void 0 : redactText(rawRequestId, this.#accessToken);
|
|
174
|
+
const code = rawCode === void 0 ? void 0 : redactText(rawCode, this.#accessToken);
|
|
175
|
+
const retryable = typeof errorPayload.retryable === "boolean" ? errorPayload.retryable : response.status >= 500;
|
|
176
|
+
throw new MitraApiError(message, response.status, {
|
|
177
|
+
...code === void 0 ? {} : { code },
|
|
178
|
+
...errorPayload.details === void 0 ? {} : { details: redactDetails(errorPayload.details, this.#accessToken) },
|
|
179
|
+
...requestId === void 0 ? {} : { requestId },
|
|
180
|
+
...retryable === void 0 ? {} : { retryable }
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return payload;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (error instanceof MitraApiError) throw error;
|
|
186
|
+
if (controller.signal.aborted) {
|
|
187
|
+
throw new MitraApiError("Mitra request timed out", 0, {
|
|
188
|
+
code: "REQUEST_TIMEOUT",
|
|
189
|
+
retryable: true
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
throw new MitraApiError("The Mitra API request failed", 0, {
|
|
193
|
+
code: "NETWORK_ERROR",
|
|
194
|
+
retryable: true
|
|
195
|
+
});
|
|
196
|
+
} finally {
|
|
197
|
+
clearTimeout(timeout);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
get(path, params) {
|
|
201
|
+
return this.request(path, { method: "GET", ...params === void 0 ? {} : { params } });
|
|
202
|
+
}
|
|
203
|
+
post(path, body, headers) {
|
|
204
|
+
return this.request(path, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
...body === void 0 ? {} : { body },
|
|
207
|
+
...headers === void 0 ? {} : { headers }
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
put(path, body) {
|
|
211
|
+
return this.request(path, { method: "PUT", body });
|
|
212
|
+
}
|
|
213
|
+
delete(path, params) {
|
|
214
|
+
return this.request(path, {
|
|
215
|
+
method: "DELETE",
|
|
216
|
+
...params === void 0 ? {} : { params }
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/client.ts
|
|
222
|
+
var coreErrors = {
|
|
223
|
+
configuration: (message) => new MitraConfigurationError(message),
|
|
224
|
+
invalidResponse: (message) => new MitraApiError(message, 200, { code: "INVALID_RESPONSE", retryable: false })
|
|
225
|
+
};
|
|
226
|
+
var DefaultMitraClient = class {
|
|
227
|
+
auth;
|
|
228
|
+
entities;
|
|
229
|
+
functions;
|
|
230
|
+
integration;
|
|
231
|
+
queries;
|
|
232
|
+
#dataSourceId;
|
|
233
|
+
#initPromise;
|
|
234
|
+
#appId;
|
|
235
|
+
#codeStudioHttpClient;
|
|
236
|
+
constructor(config) {
|
|
237
|
+
this.#appId = config.appId;
|
|
238
|
+
this.#dataSourceId = config.dataSourceId;
|
|
239
|
+
const httpClient = (service) => new HttpClient({
|
|
240
|
+
baseUrl: `${config.apiUrl}/${service}`,
|
|
241
|
+
accessToken: config.accessToken,
|
|
242
|
+
appId: config.appId,
|
|
243
|
+
timeoutMs: config.timeoutMs,
|
|
244
|
+
fetch: config.fetch
|
|
245
|
+
});
|
|
246
|
+
const core = createSdkCore({
|
|
247
|
+
transports: {
|
|
248
|
+
auth: httpClient("iam"),
|
|
249
|
+
dataManager: httpClient("data-manager"),
|
|
250
|
+
functions: httpClient("functions"),
|
|
251
|
+
integration: httpClient("integration")
|
|
252
|
+
},
|
|
253
|
+
getDataSourceId: () => this.#dataSourceId,
|
|
254
|
+
functions: { executeInvocationType: "sync", emptyInput: "empty-object" },
|
|
255
|
+
errors: coreErrors
|
|
256
|
+
});
|
|
257
|
+
this.auth = core.auth;
|
|
258
|
+
this.entities = core.entities;
|
|
259
|
+
this.functions = core.functions;
|
|
260
|
+
this.integration = core.integration;
|
|
261
|
+
this.queries = core.queries;
|
|
262
|
+
this.#codeStudioHttpClient = httpClient("code-studio");
|
|
263
|
+
}
|
|
264
|
+
init() {
|
|
265
|
+
if (this.#dataSourceId) return Promise.resolve();
|
|
266
|
+
if (!this.#initPromise) {
|
|
267
|
+
this.#initPromise = this.resolveDataSourceId().catch((error) => {
|
|
268
|
+
this.#initPromise = void 0;
|
|
269
|
+
throw error;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return this.#initPromise;
|
|
273
|
+
}
|
|
274
|
+
async resolveDataSourceId() {
|
|
275
|
+
const appInfo = expectObject(
|
|
276
|
+
await this.#codeStudioHttpClient.get(
|
|
277
|
+
`/api/v1/apps/${encodePathSegment(this.#appId, "appId", coreErrors)}/info`
|
|
278
|
+
),
|
|
279
|
+
"App info response",
|
|
280
|
+
coreErrors
|
|
281
|
+
);
|
|
282
|
+
if (!appInfo.dataSourceId?.trim()) {
|
|
283
|
+
throw new MitraApiError("The app info response does not include a dataSourceId", 200, {
|
|
284
|
+
code: "INVALID_RESPONSE",
|
|
285
|
+
retryable: false
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
this.#dataSourceId = appInfo.dataSourceId;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
function createClient(config = {}) {
|
|
292
|
+
return new DefaultMitraClient(resolveConfig(config));
|
|
293
|
+
}
|
|
294
|
+
function createClientFromEnvironment(environment) {
|
|
295
|
+
return new DefaultMitraClient(resolveConfig({}, environment));
|
|
296
|
+
}
|
|
297
|
+
export {
|
|
298
|
+
MitraApiError,
|
|
299
|
+
MitraConfigurationError,
|
|
300
|
+
createClient,
|
|
301
|
+
createClientFromEnvironment
|
|
302
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mitralab.io/functions-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "JavaScript and TypeScript SDK for Mitra Server Functions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"contracts",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"CHANGELOG.md"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public",
|
|
31
|
+
"registry": "https://registry.npmjs.org"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
35
|
+
"format": "prettier --write .",
|
|
36
|
+
"format:check": "prettier --check .",
|
|
37
|
+
"lint": "eslint .",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run --coverage",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"check:exports": "node scripts/check-exports.mjs",
|
|
42
|
+
"check:contracts": "node scripts/check-contract-corpus.mjs",
|
|
43
|
+
"pack:check": "npm pack --dry-run",
|
|
44
|
+
"smoke:package": "node scripts/smoke-package.mjs",
|
|
45
|
+
"check": "npm run format:check && npm run lint && npm run typecheck && npm test && npm run build && npm run check:exports && npm run check:contracts && npm run pack:check && npm run smoke:package",
|
|
46
|
+
"prepublishOnly": "npm run check"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"mitra",
|
|
50
|
+
"sdk",
|
|
51
|
+
"serverless",
|
|
52
|
+
"functions"
|
|
53
|
+
],
|
|
54
|
+
"author": "Mitra Platform",
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "https://github.com/mitralab-dev/mitra-functions-sdk-js"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/mitralab-dev/mitra-functions-sdk-js#readme",
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": "https://github.com/mitralab-dev/mitra-functions-sdk-js/issues"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=18.0.0"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@mitralab.io/sdk-core": "^0.1.0"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@eslint/js": "^9.39.1",
|
|
72
|
+
"@types/node": "^22.15.0",
|
|
73
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
74
|
+
"eslint": "^9.39.1",
|
|
75
|
+
"globals": "^16.5.0",
|
|
76
|
+
"prettier": "^3.8.1",
|
|
77
|
+
"tsup": "^8.5.1",
|
|
78
|
+
"typescript": "~5.9.3",
|
|
79
|
+
"typescript-eslint": "~8.55.0",
|
|
80
|
+
"vite": "~6.4.3",
|
|
81
|
+
"vitest": "^3.2.4"
|
|
82
|
+
},
|
|
83
|
+
"overrides": {
|
|
84
|
+
"esbuild": "^0.25.0",
|
|
85
|
+
"test-exclude": "7.0.1"
|
|
86
|
+
}
|
|
87
|
+
}
|