sharetribe-flex-build-sdk 1.15.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/README.md +222 -0
- package/build.js +49 -0
- package/package.json +54 -0
- package/src/api/client.ts +218 -0
- package/src/api/http-client.ts +135 -0
- package/src/api/multipart.ts +78 -0
- package/src/api/transit.ts +96 -0
- package/src/assets.ts +116 -0
- package/src/auth-storage.ts +77 -0
- package/src/deploy.ts +96 -0
- package/src/edn-process.ts +126 -0
- package/src/events.ts +203 -0
- package/src/index.ts +101 -0
- package/src/listing-approval.ts +59 -0
- package/src/notifications.ts +89 -0
- package/src/processes.ts +320 -0
- package/src/sdk-exports.md +25 -0
- package/src/search.ts +273 -0
- package/src/stripe.ts +39 -0
- package/src/types/jsedn.d.ts +9 -0
- package/src/types/transit-js.d.ts +10 -0
- package/src/types.ts +38 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Sharetribe Flex Build SDK
|
|
2
|
+
|
|
3
|
+
SDK for building and managing Sharetribe Flex transaction processes programmatically.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install sharetribe-flex-build-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Process Management**: Programmatic API for all process commands (list, create, push, pull, aliases)
|
|
14
|
+
- **API Client Functions**: Make authenticated API calls to Sharetribe Build API
|
|
15
|
+
- **Transit Support**: Full support for Transit format encoding/decoding
|
|
16
|
+
- **EDN Parser**: Parse and serialize process.edn files
|
|
17
|
+
- **HTTP Client**: Pure Node.js HTTP client (no external dependencies except transit-js/jsedn)
|
|
18
|
+
- **TypeScript**: Full type definitions included
|
|
19
|
+
- **1-to-1 with CLI**: Functions match CLI command capabilities exactly
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
The SDK provides programmatic access to all CLI capabilities:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import {
|
|
27
|
+
listProcesses,
|
|
28
|
+
getProcess,
|
|
29
|
+
createProcess,
|
|
30
|
+
pushProcess,
|
|
31
|
+
createAlias
|
|
32
|
+
} from 'sharetribe-flex-build-sdk';
|
|
33
|
+
|
|
34
|
+
const apiKey = 'your-api-key';
|
|
35
|
+
const marketplace = 'your-marketplace-id';
|
|
36
|
+
|
|
37
|
+
// List all processes
|
|
38
|
+
const processes = await listProcesses(apiKey, marketplace);
|
|
39
|
+
|
|
40
|
+
// Get a specific process
|
|
41
|
+
const process = await getProcess(apiKey, marketplace, 'my-process');
|
|
42
|
+
|
|
43
|
+
// Create new process
|
|
44
|
+
await createProcess(apiKey, marketplace, 'new-process', ednDefinition);
|
|
45
|
+
|
|
46
|
+
// Push an update
|
|
47
|
+
await pushProcess(apiKey, marketplace, 'my-process', updatedDefinition, templates);
|
|
48
|
+
|
|
49
|
+
// Create alias
|
|
50
|
+
await createAlias(apiKey, marketplace, 'my-process', 1, 'release');
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
### Process Management (CLI Equivalents)
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import {
|
|
59
|
+
listProcesses,
|
|
60
|
+
listProcessVersions,
|
|
61
|
+
getProcess,
|
|
62
|
+
createProcess,
|
|
63
|
+
pushProcess,
|
|
64
|
+
createAlias,
|
|
65
|
+
updateAlias,
|
|
66
|
+
deleteAlias
|
|
67
|
+
} from 'sharetribe-flex-build-sdk';
|
|
68
|
+
|
|
69
|
+
// List processes (flex-cli: process list)
|
|
70
|
+
const processes = await listProcesses(apiKey, marketplace);
|
|
71
|
+
// Returns: [{ name: 'instant-booking', version: 3 }, ...]
|
|
72
|
+
|
|
73
|
+
// List versions (flex-cli: process list --process my-process)
|
|
74
|
+
const versions = await listProcessVersions(apiKey, marketplace, 'my-process');
|
|
75
|
+
// Returns: [{ version: 1, createdAt: '...', aliases: ['release'], transactionCount: 42 }, ...]
|
|
76
|
+
|
|
77
|
+
// Get process details (flex-cli: process pull)
|
|
78
|
+
const details = await getProcess(apiKey, marketplace, 'my-process', { version: '2' });
|
|
79
|
+
// Returns: { definition: '...', version: 2, emailTemplates: [...] }
|
|
80
|
+
|
|
81
|
+
// Create process (flex-cli: process create)
|
|
82
|
+
const result = await createProcess(apiKey, marketplace, 'new-process', ednString);
|
|
83
|
+
// Returns: { name: 'new-process', version: 1 }
|
|
84
|
+
|
|
85
|
+
// Push update (flex-cli: process push)
|
|
86
|
+
const pushResult = await pushProcess(apiKey, marketplace, 'my-process', ednString, templates);
|
|
87
|
+
// Returns: { version: 2 } or { noChanges: true }
|
|
88
|
+
|
|
89
|
+
// Manage aliases (flex-cli: process create-alias, update-alias)
|
|
90
|
+
await createAlias(apiKey, marketplace, 'my-process', 1, 'release');
|
|
91
|
+
await updateAlias(apiKey, marketplace, 'my-process', 2, 'release');
|
|
92
|
+
await deleteAlias(apiKey, marketplace, 'my-process', 'release');
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Parse EDN Process Files
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { parseProcessFile, serializeProcess } from 'sharetribe-flex-build-sdk';
|
|
99
|
+
|
|
100
|
+
// Parse a process.edn file
|
|
101
|
+
const process = parseProcessFile('./process.edn');
|
|
102
|
+
console.log(process.name, process.states, process.transitions);
|
|
103
|
+
|
|
104
|
+
// Serialize back to EDN format
|
|
105
|
+
const ednString = serializeProcess(process);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Make API Calls
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { apiGet, apiPost, apiPostTransit } from 'sharetribe-flex-build-sdk';
|
|
112
|
+
|
|
113
|
+
const apiKey = 'your-api-key';
|
|
114
|
+
const marketplace = 'your-marketplace-id';
|
|
115
|
+
|
|
116
|
+
// List processes
|
|
117
|
+
const response = await apiGet(apiKey, '/processes/query', { marketplace });
|
|
118
|
+
|
|
119
|
+
// Create a process
|
|
120
|
+
await apiPost(apiKey, '/processes/create', { marketplace }, {
|
|
121
|
+
process: 'my-process-name'
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Push a process update (Transit format)
|
|
125
|
+
await apiPostTransit(apiKey, '/processes/update',
|
|
126
|
+
{ marketplace, process: 'my-process' },
|
|
127
|
+
processData
|
|
128
|
+
);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Transit Utilities
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { encodeTransit, decodeTransit, keyword, keywordMap } from 'sharetribe-flex-build-sdk';
|
|
135
|
+
|
|
136
|
+
// Create Transit keywords (Clojure-style)
|
|
137
|
+
const processName = keyword('instant-booking');
|
|
138
|
+
|
|
139
|
+
// Create Transit maps with keyword keys
|
|
140
|
+
const transitMap = keywordMap({
|
|
141
|
+
name: keyword('my-process'),
|
|
142
|
+
version: 1
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Encode/decode Transit format
|
|
146
|
+
const encoded = encodeTransit(transitMap);
|
|
147
|
+
const decoded = decodeTransit(encoded);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Type Definitions
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import type {
|
|
154
|
+
ProcessDefinition,
|
|
155
|
+
ProcessState,
|
|
156
|
+
ProcessTransition,
|
|
157
|
+
ProcessNotification,
|
|
158
|
+
ApiError,
|
|
159
|
+
HttpResponse
|
|
160
|
+
} from 'sharetribe-flex-build-sdk';
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## API Reference
|
|
164
|
+
|
|
165
|
+
### API Functions
|
|
166
|
+
|
|
167
|
+
- `apiGet(apiKey, endpoint, queryParams?)` - Make GET request
|
|
168
|
+
- `apiPost(apiKey, endpoint, queryParams?, body?)` - Make POST request
|
|
169
|
+
- `apiDelete(apiKey, endpoint, queryParams?)` - Make DELETE request
|
|
170
|
+
- `apiPostMultipart(apiKey, endpoint, queryParams, fields)` - Make multipart POST
|
|
171
|
+
- `apiPostTransit(apiKey, endpoint, queryParams, body)` - Make Transit-encoded POST
|
|
172
|
+
|
|
173
|
+
### EDN Functions
|
|
174
|
+
|
|
175
|
+
- `parseProcessFile(filePath)` - Parse process.edn file
|
|
176
|
+
- `serializeProcess(process)` - Serialize to EDN format
|
|
177
|
+
|
|
178
|
+
### Transit Functions
|
|
179
|
+
|
|
180
|
+
- `encodeTransit(data)` - Encode to Transit JSON
|
|
181
|
+
- `decodeTransit(transitString)` - Decode from Transit JSON
|
|
182
|
+
- `keyword(name)` - Create Transit keyword
|
|
183
|
+
- `keywordMap(obj)` - Create Transit map with keyword keys
|
|
184
|
+
|
|
185
|
+
### HTTP Functions
|
|
186
|
+
|
|
187
|
+
- `request(url, options)` - Low-level HTTP request
|
|
188
|
+
- `get(url, headers?)` - HTTP GET
|
|
189
|
+
- `post(url, data, headers?)` - HTTP POST with JSON
|
|
190
|
+
- `postTransit(url, body, headers?)` - HTTP POST with Transit
|
|
191
|
+
- `del(url, headers?)` - HTTP DELETE
|
|
192
|
+
|
|
193
|
+
## CLI vs SDK
|
|
194
|
+
|
|
195
|
+
The SDK provides programmatic access to CLI capabilities:
|
|
196
|
+
|
|
197
|
+
| CLI Command | SDK Function |
|
|
198
|
+
|-------------|--------------|
|
|
199
|
+
| `process list` | `listProcesses()` |
|
|
200
|
+
| `process list --process NAME` | `listProcessVersions()` |
|
|
201
|
+
| `process pull` | `getProcess()` |
|
|
202
|
+
| `process create` | `createProcess()` |
|
|
203
|
+
| `process push` | `pushProcess()` |
|
|
204
|
+
| `process create-alias` | `createAlias()` |
|
|
205
|
+
| `process update-alias` | `updateAlias()` |
|
|
206
|
+
| `process delete-alias` | `deleteAlias()` |
|
|
207
|
+
|
|
208
|
+
**Note**: For v1.15.0, the SDK focuses on process management (the core functionality). The CLI includes additional commands (search, assets, notifications, listing-approval, stripe, events) which use the lower-level API client functions exported by this SDK. Future versions may add higher-level wrappers for these commands.
|
|
209
|
+
|
|
210
|
+
## Related Packages
|
|
211
|
+
|
|
212
|
+
- [sharetribe-cli](https://www.npmjs.com/package/sharetribe-cli) - Command-line interface (v1.15.0, depends on this SDK)
|
|
213
|
+
|
|
214
|
+
## Version Relationship
|
|
215
|
+
|
|
216
|
+
- CLI version 1.15.0 depends on SDK version 1.15.0 exactly
|
|
217
|
+
- Both packages are maintained in the same [monorepo](https://github.com/jayenashar/sharetribe-cli)
|
|
218
|
+
- Versions are kept in sync (1-to-1 relationship)
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
Apache-2.0
|
package/build.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build script for sharetribe-flex-build-sdk using esbuild
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as esbuild from 'esbuild';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
|
|
8
|
+
const pkg = JSON.parse(readFileSync('./package.json', 'utf-8'));
|
|
9
|
+
|
|
10
|
+
// Parse command line arguments
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const isWatch = args.includes('--watch');
|
|
13
|
+
const isDev = args.includes('--dev');
|
|
14
|
+
|
|
15
|
+
const buildOptions = {
|
|
16
|
+
entryPoints: ['src/index.ts'],
|
|
17
|
+
bundle: true,
|
|
18
|
+
platform: 'node',
|
|
19
|
+
target: 'node18',
|
|
20
|
+
format: 'esm',
|
|
21
|
+
outfile: 'dist/index.js',
|
|
22
|
+
sourcemap: isDev,
|
|
23
|
+
minify: !isDev,
|
|
24
|
+
external: [
|
|
25
|
+
...Object.keys(pkg.dependencies || {}),
|
|
26
|
+
...Object.keys(pkg.peerDependencies || {}),
|
|
27
|
+
],
|
|
28
|
+
banner: {
|
|
29
|
+
js: '#!/usr/bin/env node',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
async function build() {
|
|
34
|
+
try {
|
|
35
|
+
if (isWatch) {
|
|
36
|
+
const ctx = await esbuild.context(buildOptions);
|
|
37
|
+
await ctx.watch();
|
|
38
|
+
console.log('Watching for changes...');
|
|
39
|
+
} else {
|
|
40
|
+
await esbuild.build(buildOptions);
|
|
41
|
+
console.log('Build complete!');
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Build failed:', error);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
build();
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sharetribe-flex-build-sdk",
|
|
3
|
+
"version": "1.15.0",
|
|
4
|
+
"description": "SDK for building and managing Sharetribe Flex transaction processes",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"lint": "eslint src/**/*.ts",
|
|
22
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
23
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"sharetribe",
|
|
29
|
+
"flex",
|
|
30
|
+
"sdk",
|
|
31
|
+
"marketplace",
|
|
32
|
+
"transaction-process",
|
|
33
|
+
"build-api"
|
|
34
|
+
],
|
|
35
|
+
"author": "",
|
|
36
|
+
"license": "Apache-2.0",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/jayenashar/sharetribe-flex-build-sdk"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"jsedn": "^0.4.1",
|
|
43
|
+
"transit-js": "^0.8.874"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.17.10",
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
|
48
|
+
"@typescript-eslint/parser": "^8.18.2",
|
|
49
|
+
"eslint": "^9.17.0",
|
|
50
|
+
"prettier": "^3.4.2",
|
|
51
|
+
"typescript": "^5.7.2",
|
|
52
|
+
"vitest": "^4.0.16"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sharetribe API client
|
|
3
|
+
*
|
|
4
|
+
* Handles authentication and API requests to Sharetribe backend
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { get, post, del, request, postTransit, HttpResponse } from './http-client.js';
|
|
8
|
+
import { createMultipartBody, MultipartField } from './multipart.js';
|
|
9
|
+
import { encodeTransit, decodeTransit } from './transit.js';
|
|
10
|
+
import { readAuth } from '../auth-storage.js';
|
|
11
|
+
|
|
12
|
+
// Re-export MultipartField for use in commands
|
|
13
|
+
export type { MultipartField };
|
|
14
|
+
|
|
15
|
+
// API base URL - must match flex-cli exactly
|
|
16
|
+
const API_BASE_URL = 'https://flex-build-api.sharetribe.com/v1/build-api';
|
|
17
|
+
|
|
18
|
+
export interface ApiError {
|
|
19
|
+
code: string;
|
|
20
|
+
message: string;
|
|
21
|
+
status: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Gets authentication headers from provided API key or auth file
|
|
26
|
+
*
|
|
27
|
+
* @param apiKey - Optional Sharetribe API key. If not provided, reads from ~/.config/flex-cli/auth.edn
|
|
28
|
+
* @returns Authorization headers
|
|
29
|
+
*/
|
|
30
|
+
function getAuthHeaders(apiKey?: string): Record<string, string> {
|
|
31
|
+
let key = apiKey;
|
|
32
|
+
|
|
33
|
+
if (!key) {
|
|
34
|
+
const auth = readAuth();
|
|
35
|
+
if (!auth) {
|
|
36
|
+
throw new Error('Not logged in. Please provide apiKey or run: sharetribe-cli login');
|
|
37
|
+
}
|
|
38
|
+
key = auth.apiKey;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
Authorization: `Apikey ${key}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Handles API response and errors
|
|
48
|
+
*/
|
|
49
|
+
function handleResponse<T>(response: HttpResponse): T {
|
|
50
|
+
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
51
|
+
if (response.body) {
|
|
52
|
+
return JSON.parse(response.body) as T;
|
|
53
|
+
}
|
|
54
|
+
return {} as T;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Parse error response
|
|
58
|
+
let errorData: { errors?: Array<{ code: string; message?: string }> } = {};
|
|
59
|
+
try {
|
|
60
|
+
errorData = JSON.parse(response.body);
|
|
61
|
+
} catch {
|
|
62
|
+
// Ignore parse errors
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const firstError = errorData.errors?.[0];
|
|
66
|
+
const error: ApiError = {
|
|
67
|
+
code: firstError?.code || 'UNKNOWN_ERROR',
|
|
68
|
+
message: firstError?.message || `HTTP ${response.statusCode}`,
|
|
69
|
+
status: response.statusCode,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Makes a GET request to the API
|
|
77
|
+
*
|
|
78
|
+
* @param apiKey - Optional Sharetribe API key. If not provided, reads from ~/.config/flex-cli/auth.edn
|
|
79
|
+
* @param endpoint - API endpoint path
|
|
80
|
+
* @param queryParams - Optional query parameters
|
|
81
|
+
* @returns API response
|
|
82
|
+
*/
|
|
83
|
+
export async function apiGet<T>(apiKey: string | undefined, endpoint: string, queryParams?: Record<string, string>): Promise<T> {
|
|
84
|
+
const url = new URL(API_BASE_URL + endpoint);
|
|
85
|
+
if (queryParams) {
|
|
86
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
87
|
+
url.searchParams.append(key, value);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const response = await get(url.toString(), getAuthHeaders(apiKey));
|
|
92
|
+
return handleResponse<T>(response);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Makes a POST request to the API
|
|
97
|
+
*
|
|
98
|
+
* @param apiKey - Optional Sharetribe API key. If not provided, reads from ~/.config/flex-cli/auth.edn
|
|
99
|
+
* @param endpoint - API endpoint path
|
|
100
|
+
* @param queryParams - Optional query parameters
|
|
101
|
+
* @param body - Request body
|
|
102
|
+
* @returns API response
|
|
103
|
+
*/
|
|
104
|
+
export async function apiPost<T>(
|
|
105
|
+
apiKey: string | undefined,
|
|
106
|
+
endpoint: string,
|
|
107
|
+
queryParams?: Record<string, string>,
|
|
108
|
+
body?: unknown
|
|
109
|
+
): Promise<T> {
|
|
110
|
+
const url = new URL(API_BASE_URL + endpoint);
|
|
111
|
+
if (queryParams) {
|
|
112
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
113
|
+
url.searchParams.append(key, value);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const response = await post(url.toString(), body, getAuthHeaders(apiKey));
|
|
118
|
+
return handleResponse<T>(response);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Makes a DELETE request to the API
|
|
123
|
+
*
|
|
124
|
+
* @param apiKey - Optional Sharetribe API key. If not provided, reads from ~/.config/flex-cli/auth.edn
|
|
125
|
+
* @param endpoint - API endpoint path
|
|
126
|
+
* @param queryParams - Optional query parameters
|
|
127
|
+
* @returns API response
|
|
128
|
+
*/
|
|
129
|
+
export async function apiDelete<T>(
|
|
130
|
+
apiKey: string | undefined,
|
|
131
|
+
endpoint: string,
|
|
132
|
+
queryParams?: Record<string, string>
|
|
133
|
+
): Promise<T> {
|
|
134
|
+
const url = new URL(API_BASE_URL + endpoint);
|
|
135
|
+
if (queryParams) {
|
|
136
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
137
|
+
url.searchParams.append(key, value);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const response = await del(url.toString(), getAuthHeaders(apiKey));
|
|
142
|
+
return handleResponse<T>(response);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Makes a multipart form-data POST request to the API
|
|
147
|
+
*
|
|
148
|
+
* @param apiKey - Optional Sharetribe API key. If not provided, reads from ~/.config/flex-cli/auth.edn
|
|
149
|
+
* @param endpoint - API endpoint path
|
|
150
|
+
* @param queryParams - Query parameters
|
|
151
|
+
* @param fields - Multipart form fields
|
|
152
|
+
* @returns API response
|
|
153
|
+
*/
|
|
154
|
+
export async function apiPostMultipart<T>(
|
|
155
|
+
apiKey: string | undefined,
|
|
156
|
+
endpoint: string,
|
|
157
|
+
queryParams: Record<string, string>,
|
|
158
|
+
fields: MultipartField[]
|
|
159
|
+
): Promise<T> {
|
|
160
|
+
const url = new URL(API_BASE_URL + endpoint);
|
|
161
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
162
|
+
url.searchParams.append(key, value);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const { body, contentType } = createMultipartBody(fields);
|
|
166
|
+
|
|
167
|
+
const headers = {
|
|
168
|
+
...getAuthHeaders(apiKey),
|
|
169
|
+
'Content-Type': contentType,
|
|
170
|
+
'Content-Length': body.length.toString(),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const response = await request(url.toString(), {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
headers,
|
|
176
|
+
body,
|
|
177
|
+
});
|
|
178
|
+
return handleResponse<T>(response);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Makes a POST request to the API with Transit-encoded body
|
|
183
|
+
* Transit is a data format used by the Sharetribe API for certain endpoints
|
|
184
|
+
*
|
|
185
|
+
* @param apiKey - Optional Sharetribe API key. If not provided, reads from ~/.config/flex-cli/auth.edn
|
|
186
|
+
* @param endpoint - API endpoint path
|
|
187
|
+
* @param queryParams - Query parameters
|
|
188
|
+
* @param body - Request body (will be Transit-encoded)
|
|
189
|
+
* @returns API response
|
|
190
|
+
*/
|
|
191
|
+
export async function apiPostTransit<T>(
|
|
192
|
+
apiKey: string | undefined,
|
|
193
|
+
endpoint: string,
|
|
194
|
+
queryParams: Record<string, string>,
|
|
195
|
+
body: unknown
|
|
196
|
+
): Promise<T> {
|
|
197
|
+
const url = new URL(API_BASE_URL + endpoint);
|
|
198
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
199
|
+
url.searchParams.append(key, value);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const transitBody = encodeTransit(body);
|
|
203
|
+
const response = await postTransit(url.toString(), transitBody, getAuthHeaders(apiKey));
|
|
204
|
+
|
|
205
|
+
// Parse Transit response
|
|
206
|
+
if (response.statusCode >= 400) {
|
|
207
|
+
const errorData = decodeTransit(response.body) as any;
|
|
208
|
+
const firstError = errorData.errors?.[0];
|
|
209
|
+
const error: ApiError = {
|
|
210
|
+
code: firstError?.code || 'UNKNOWN_ERROR',
|
|
211
|
+
message: firstError?.title || `HTTP ${response.statusCode}`,
|
|
212
|
+
status: response.statusCode,
|
|
213
|
+
};
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return decodeTransit(response.body) as T;
|
|
218
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client using Node.js built-in http/https modules
|
|
3
|
+
*
|
|
4
|
+
* No external dependencies - pure Node.js implementation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as https from 'node:https';
|
|
8
|
+
import * as http from 'node:http';
|
|
9
|
+
import { URL } from 'node:url';
|
|
10
|
+
|
|
11
|
+
export interface HttpOptions {
|
|
12
|
+
method?: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
body?: string | Buffer;
|
|
15
|
+
timeout?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface HttpResponse {
|
|
19
|
+
statusCode: number;
|
|
20
|
+
headers: http.IncomingHttpHeaders;
|
|
21
|
+
body: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Makes an HTTP/HTTPS request using Node.js built-in modules
|
|
26
|
+
*
|
|
27
|
+
* Returns a promise that resolves with the response
|
|
28
|
+
*/
|
|
29
|
+
export function request(url: string, options: HttpOptions = {}): Promise<HttpResponse> {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const parsedUrl = new URL(url);
|
|
32
|
+
const isHttps = parsedUrl.protocol === 'https:';
|
|
33
|
+
const client = isHttps ? https : http;
|
|
34
|
+
|
|
35
|
+
const requestOptions: http.RequestOptions = {
|
|
36
|
+
hostname: parsedUrl.hostname,
|
|
37
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
38
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
39
|
+
method: options.method || 'GET',
|
|
40
|
+
headers: options.headers || {},
|
|
41
|
+
timeout: options.timeout || 30000,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const req = client.request(requestOptions, (res) => {
|
|
45
|
+
const chunks: Buffer[] = [];
|
|
46
|
+
|
|
47
|
+
res.on('data', (chunk: Buffer) => {
|
|
48
|
+
chunks.push(chunk);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
res.on('end', () => {
|
|
52
|
+
const body = Buffer.concat(chunks).toString('utf-8');
|
|
53
|
+
resolve({
|
|
54
|
+
statusCode: res.statusCode || 0,
|
|
55
|
+
headers: res.headers,
|
|
56
|
+
body,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
req.on('error', (error) => {
|
|
62
|
+
reject(error);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
req.on('timeout', () => {
|
|
66
|
+
req.destroy();
|
|
67
|
+
reject(new Error('Request timeout'));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (options.body) {
|
|
71
|
+
req.write(options.body);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
req.end();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Makes a GET request
|
|
80
|
+
*/
|
|
81
|
+
export function get(url: string, headers?: Record<string, string>): Promise<HttpResponse> {
|
|
82
|
+
return request(url, { method: 'GET', headers });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Makes a POST request with JSON body
|
|
87
|
+
*/
|
|
88
|
+
export function post(
|
|
89
|
+
url: string,
|
|
90
|
+
data: unknown,
|
|
91
|
+
headers?: Record<string, string>
|
|
92
|
+
): Promise<HttpResponse> {
|
|
93
|
+
const body = JSON.stringify(data);
|
|
94
|
+
const allHeaders = {
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
'Content-Length': Buffer.byteLength(body).toString(),
|
|
97
|
+
...headers,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return request(url, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: allHeaders,
|
|
103
|
+
body,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Makes a POST request with Transit body
|
|
109
|
+
* Transit is a data format used by the Sharetribe API for certain endpoints
|
|
110
|
+
*/
|
|
111
|
+
export function postTransit(
|
|
112
|
+
url: string,
|
|
113
|
+
body: string,
|
|
114
|
+
headers?: Record<string, string>
|
|
115
|
+
): Promise<HttpResponse> {
|
|
116
|
+
const allHeaders = {
|
|
117
|
+
'Content-Type': 'application/transit+json',
|
|
118
|
+
'Accept': 'application/transit+json',
|
|
119
|
+
'Content-Length': Buffer.byteLength(body).toString(),
|
|
120
|
+
...headers,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return request(url, {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: allHeaders,
|
|
126
|
+
body,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Makes a DELETE request
|
|
132
|
+
*/
|
|
133
|
+
export function del(url: string, headers?: Record<string, string>): Promise<HttpResponse> {
|
|
134
|
+
return request(url, { method: 'DELETE', headers });
|
|
135
|
+
}
|