@thisux/dunsocial-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/LICENSE +21 -0
- package/README.md +168 -0
- package/dist/client.d.ts +39 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +188 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +72 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/media-limits.d.ts +16 -0
- package/dist/media-limits.d.ts.map +1 -0
- package/dist/media-limits.js +49 -0
- package/dist/pagination.d.ts +4 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +35 -0
- package/dist/resources/accounts.d.ts +22 -0
- package/dist/resources/accounts.d.ts.map +1 -0
- package/dist/resources/accounts.js +27 -0
- package/dist/resources/drafts.d.ts +16 -0
- package/dist/resources/drafts.d.ts.map +1 -0
- package/dist/resources/drafts.js +46 -0
- package/dist/resources/media.d.ts +15 -0
- package/dist/resources/media.d.ts.map +1 -0
- package/dist/resources/media.js +41 -0
- package/dist/resources/memory.d.ts +25 -0
- package/dist/resources/memory.d.ts.map +1 -0
- package/dist/resources/memory.js +40 -0
- package/dist/resources/personalization.d.ts +11 -0
- package/dist/resources/personalization.d.ts.map +1 -0
- package/dist/resources/personalization.js +22 -0
- package/dist/resources/posts.d.ts +27 -0
- package/dist/resources/posts.d.ts.map +1 -0
- package/dist/resources/posts.js +114 -0
- package/dist/resources/workspaces.d.ts +9 -0
- package/dist/resources/workspaces.d.ts.map +1 -0
- package/dist/resources/workspaces.js +13 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1 -0
- package/dist/webhooks.d.ts +3 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +62 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) DunSocial / THISUX
|
|
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,168 @@
|
|
|
1
|
+
# @thisux/dunsocial-sdk
|
|
2
|
+
|
|
3
|
+
TypeScript client for the DunSocial REST API. Schedule and publish posts, upload media, search brand memory, and verify webhook signatures.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { DunSocial } from '@thisux/dunsocial-sdk';
|
|
7
|
+
|
|
8
|
+
const dun = new DunSocial({
|
|
9
|
+
token: process.env.DUN_TOKEN,
|
|
10
|
+
workspaceId: process.env.DUN_WORKSPACE_ID
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
await dun.posts.schedule({
|
|
14
|
+
content: 'Shipping the TypeScript SDK.',
|
|
15
|
+
socialAccountIds: ['acc_x'],
|
|
16
|
+
scheduledAt: new Date(Date.now() + 60 * 60 * 1000)
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires Node 20+, Bun, or Cloudflare Workers. The package uses `fetch` and Web Crypto only.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @thisux/dunsocial-sdk
|
|
26
|
+
# bun add @thisux/dunsocial-sdk
|
|
27
|
+
# pnpm add @thisux/dunsocial-sdk
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Create a personal access token in the app under **Settings → CLI**. Format: `dun_pat_…`.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export DUN_TOKEN=dun_pat_…
|
|
34
|
+
export DUN_WORKSPACE_ID=your_workspace_id
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Constructor options override env. Names match the CLI: `DUN_TOKEN`, `DUN_WORKSPACE_ID`, `DUN_API_URL`.
|
|
38
|
+
|
|
39
|
+
## Auth
|
|
40
|
+
|
|
41
|
+
Every request sends `Authorization: Bearer <token>` and `User-Agent: dunsocial-sdk/<version>`. Workspace-scoped methods also send `X-Workspace-Id`.
|
|
42
|
+
|
|
43
|
+
| Method | Scope |
|
|
44
|
+
|--------|--------|
|
|
45
|
+
| `workspaces.list` / `get` | `workspace:read` |
|
|
46
|
+
| `accounts.list` | `workspace:read` |
|
|
47
|
+
| `personalization.get` | `workspace:read` |
|
|
48
|
+
| `personalization.set` / `clear` | `workspace:write` |
|
|
49
|
+
| `posts.list` / `get` / `validate` | `posts:read` |
|
|
50
|
+
| `drafts.list` / `get` | `posts:read` |
|
|
51
|
+
| `posts.schedule` / `reschedule` / `cancel` / threads | `posts:schedule` |
|
|
52
|
+
| `posts.publish` / `publishThread` | `posts:publish` |
|
|
53
|
+
| `posts.delete` | `posts:delete` |
|
|
54
|
+
| `drafts.create` / `update` / `delete` | `drafts:write` |
|
|
55
|
+
| `media.list` / `get` | `media:read` |
|
|
56
|
+
| `media.upload` / `delete` | `media:write` |
|
|
57
|
+
| `memory` GET (collections / list) | `memory:read` |
|
|
58
|
+
| `memory.search` / save / collection writes | `memory:write` |
|
|
59
|
+
|
|
60
|
+
`workspaces.list` works without a workspace id. A PAT bound to one workspace still lists that workspace.
|
|
61
|
+
|
|
62
|
+
## Posts
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const [post] = await dun.posts.publish({
|
|
66
|
+
content: 'Live now.',
|
|
67
|
+
socialAccountIds: ['acc_x'],
|
|
68
|
+
mediaIds: ['media_1']
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const published = await dun.posts.wait(post.id);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`publish` queues the row (`status: scheduled`). `wait(id)` polls until `published`, `failed`, or `cancelled`.
|
|
75
|
+
|
|
76
|
+
`validate` is HTTP 200 even when the post is invalid:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const result = await dun.posts.validate({
|
|
80
|
+
content: 'Hello Reddit',
|
|
81
|
+
socialAccountIds: ['acc_reddit'],
|
|
82
|
+
metadata: { reddit: { subreddit: 'startups', title: 'Hello', kind: 'self' } }
|
|
83
|
+
});
|
|
84
|
+
// result.valid === false → result.issues[]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Pass `throwOnInvalid: true` if you want a `DunSocialError` instead.
|
|
88
|
+
|
|
89
|
+
`mediaIds` in the SDK map to API `mediaUrls`. `scheduledAt` accepts a `Date` or ISO string.
|
|
90
|
+
|
|
91
|
+
## Media
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const asset = await dun.media.upload({
|
|
95
|
+
file: new Uint8Array(await Bun.file('cover.png').arrayBuffer()),
|
|
96
|
+
filename: 'cover.png',
|
|
97
|
+
mimeType: 'image/png'
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`upload()` hides the three-step flow: request a signed URL, PUT the bytes, complete the gallery row. Images max 8MB; video max 512MB.
|
|
102
|
+
|
|
103
|
+
## Webhooks
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { DunSocial } from '@thisux/dunsocial-sdk';
|
|
107
|
+
|
|
108
|
+
export async function POST(request: Request) {
|
|
109
|
+
const rawBody = await request.text();
|
|
110
|
+
const event = await DunSocial.webhooks.constructEvent(
|
|
111
|
+
rawBody,
|
|
112
|
+
{
|
|
113
|
+
signature: request.headers.get('X-DunSocial-Signature') ?? '',
|
|
114
|
+
timestamp: request.headers.get('X-DunSocial-Timestamp') ?? ''
|
|
115
|
+
},
|
|
116
|
+
process.env.DUN_WEBHOOK_SECRET!
|
|
117
|
+
);
|
|
118
|
+
// event.type: post.published | post.publish_failed | …
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
HMAC-SHA256 of `{timestamp}.{rawBody}`. Use the raw body; the default replay window is 300 seconds.
|
|
123
|
+
|
|
124
|
+
## Errors
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { DunSocial, DunSocialError } from '@thisux/dunsocial-sdk';
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await dun.posts.schedule({ /* … */ });
|
|
131
|
+
} catch (err) {
|
|
132
|
+
if (err instanceof DunSocialError && err.code === 'subscription_required') {
|
|
133
|
+
console.log(err.billingUrl);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
| Code | HTTP | Retry? |
|
|
139
|
+
|------|------|--------|
|
|
140
|
+
| `missing_config` | — | no |
|
|
141
|
+
| `unauthorized` | 401 | no |
|
|
142
|
+
| `forbidden` | 403 | no |
|
|
143
|
+
| `not_found` | 404 | no |
|
|
144
|
+
| `subscription_required` | 402 | no — see `billingUrl` |
|
|
145
|
+
| `rate_limited` | 429 | yes (`Retry-After`) |
|
|
146
|
+
| `x_cap_exceeded` | 429 | no |
|
|
147
|
+
| `timeout` / `network` | 5xx / abort | 5xx once |
|
|
148
|
+
|
|
149
|
+
## CLI vs SDK vs MCP
|
|
150
|
+
|
|
151
|
+
| Surface | Use when |
|
|
152
|
+
|---------|----------|
|
|
153
|
+
| **SDK** (`@thisux/dunsocial-sdk`) | Your app or backend talks to DunSocial in TypeScript |
|
|
154
|
+
| **CLI** (`dunsocial` / `dun`) | Terminal, CI, local agents |
|
|
155
|
+
| **MCP** | Claude, ChatGPT, Cursor call tools in chat |
|
|
156
|
+
| **REST** | Any other language — [OpenAPI](https://api.dunsocial.com/api/openapi.json) |
|
|
157
|
+
|
|
158
|
+
The SDK does not wrap the CLI. Both call the same HTTP API.
|
|
159
|
+
|
|
160
|
+
## OpenAPI
|
|
161
|
+
|
|
162
|
+
- File: [sdks/openapi/openapi.yaml](https://github.com/thisuxhq/dunpostsapp/blob/main/sdks/openapi/openapi.yaml)
|
|
163
|
+
- Live: https://api.dunsocial.com/api/openapi.json
|
|
164
|
+
- Docs: https://www.dunsocial.com/docs/sdk
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DunSocialOptions, FetchLike } from './types';
|
|
2
|
+
export interface ApiResponse<T = unknown> {
|
|
3
|
+
success?: boolean;
|
|
4
|
+
data?: T;
|
|
5
|
+
error?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
code?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface RequestOptions {
|
|
11
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
12
|
+
path: string;
|
|
13
|
+
query?: Record<string, string | number | boolean | undefined | null>;
|
|
14
|
+
body?: unknown;
|
|
15
|
+
auth?: boolean;
|
|
16
|
+
workspace?: boolean;
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
rawBody?: Uint8Array | ArrayBuffer | Blob;
|
|
19
|
+
contentType?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare class HttpClient {
|
|
22
|
+
readonly baseUrl: string;
|
|
23
|
+
readonly token?: string;
|
|
24
|
+
readonly workspaceId?: string;
|
|
25
|
+
readonly fetchImpl: FetchLike;
|
|
26
|
+
readonly timeoutMs: number;
|
|
27
|
+
constructor(options: DunSocialOptions);
|
|
28
|
+
requireToken(): string;
|
|
29
|
+
requireWorkspace(): string;
|
|
30
|
+
get<T>(path: string, opts?: Omit<RequestOptions, 'method' | 'path'>): Promise<T>;
|
|
31
|
+
post<T>(path: string, body?: unknown, opts?: Omit<RequestOptions, 'method' | 'path' | 'body'>): Promise<T>;
|
|
32
|
+
patch<T>(path: string, body?: unknown, opts?: Omit<RequestOptions, 'method' | 'path' | 'body'>): Promise<T>;
|
|
33
|
+
delete<T>(path: string, body?: unknown, opts?: Omit<RequestOptions, 'method' | 'path' | 'body'>): Promise<T>;
|
|
34
|
+
putBytes(url: string, body: Uint8Array | ArrayBuffer | Blob, mimeType: string): Promise<void>;
|
|
35
|
+
request<T = unknown>(options: RequestOptions, attempt?: number): Promise<T>;
|
|
36
|
+
private shouldRetry;
|
|
37
|
+
private fetchWithTimeout;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC9B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;IACrE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,UAAU;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,gBAAgB;IAWrC,YAAY,IAAI,MAAM;IAStB,gBAAgB,IAAI,MAAM;IAU1B,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,MAAM,CAAC;IAInE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAI7F,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAI9F,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAIzF,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B7F,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,SAAI,GAAG,OAAO,CAAC,CAAC,CAAC;IA8F5E,OAAO,CAAC,WAAW;YAOL,gBAAgB;CAgB9B"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { SDK_VERSION } from './version';
|
|
2
|
+
import { DunSocialError, asRecord, billingUrlFrom, inferCode, isXCapError, retryAfterMsFrom } from './errors';
|
|
3
|
+
export class HttpClient {
|
|
4
|
+
baseUrl;
|
|
5
|
+
token;
|
|
6
|
+
workspaceId;
|
|
7
|
+
fetchImpl;
|
|
8
|
+
timeoutMs;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.baseUrl = (options.baseUrl ?? env('DUN_API_URL') ?? 'https://api.dunsocial.com').replace(/\/$/, '');
|
|
11
|
+
this.token = options.token ?? env('DUN_TOKEN');
|
|
12
|
+
this.workspaceId = options.workspaceId ?? env('DUN_WORKSPACE_ID');
|
|
13
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
14
|
+
this.timeoutMs = options.timeoutMs ?? 60_000;
|
|
15
|
+
}
|
|
16
|
+
requireToken() {
|
|
17
|
+
if (!this.token) {
|
|
18
|
+
throw new DunSocialError('Missing API token. Pass token or set DUN_TOKEN.', {
|
|
19
|
+
code: 'missing_config'
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return this.token;
|
|
23
|
+
}
|
|
24
|
+
requireWorkspace() {
|
|
25
|
+
if (!this.workspaceId) {
|
|
26
|
+
throw new DunSocialError('Missing workspace id. Pass workspaceId or set DUN_WORKSPACE_ID.', { code: 'missing_config' });
|
|
27
|
+
}
|
|
28
|
+
return this.workspaceId;
|
|
29
|
+
}
|
|
30
|
+
get(path, opts) {
|
|
31
|
+
return this.request({ ...opts, method: 'GET', path });
|
|
32
|
+
}
|
|
33
|
+
post(path, body, opts) {
|
|
34
|
+
return this.request({ ...opts, method: 'POST', path, body });
|
|
35
|
+
}
|
|
36
|
+
patch(path, body, opts) {
|
|
37
|
+
return this.request({ ...opts, method: 'PATCH', path, body });
|
|
38
|
+
}
|
|
39
|
+
delete(path, body, opts) {
|
|
40
|
+
return this.request({ ...opts, method: 'DELETE', path, body });
|
|
41
|
+
}
|
|
42
|
+
async putBytes(url, body, mimeType) {
|
|
43
|
+
const bytes = body instanceof Blob ? body : body instanceof ArrayBuffer ? new Uint8Array(body) : body;
|
|
44
|
+
const size = bytes instanceof Blob ? bytes.size : bytes.byteLength;
|
|
45
|
+
let response;
|
|
46
|
+
try {
|
|
47
|
+
response = await this.fetchWithTimeout(url, {
|
|
48
|
+
method: 'PUT',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': mimeType,
|
|
51
|
+
'Content-Length': String(size)
|
|
52
|
+
},
|
|
53
|
+
body: bytes
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
throw new DunSocialError(`Upload PUT failed: ${err.message}`, {
|
|
58
|
+
code: 'network',
|
|
59
|
+
details: { cause: String(err) }
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const text = await response.text().catch(() => '');
|
|
64
|
+
throw new DunSocialError(`Upload PUT failed with HTTP ${response.status}${text ? `: ${text.slice(0, 200)}` : ''}`, { status: response.status, code: 'network' });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async request(options, attempt = 0) {
|
|
68
|
+
const method = options.method ?? 'GET';
|
|
69
|
+
const url = new URL(options.path.startsWith('http') ? options.path : `${this.baseUrl}${options.path}`);
|
|
70
|
+
if (options.query) {
|
|
71
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
72
|
+
if (value === undefined || value === null || value === '')
|
|
73
|
+
continue;
|
|
74
|
+
url.searchParams.set(key, String(value));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const headers = {
|
|
78
|
+
Accept: 'application/json',
|
|
79
|
+
'User-Agent': `dunsocial-sdk/${SDK_VERSION}`,
|
|
80
|
+
...(options.headers ?? {})
|
|
81
|
+
};
|
|
82
|
+
if (options.auth !== false) {
|
|
83
|
+
headers.Authorization = `Bearer ${this.requireToken()}`;
|
|
84
|
+
}
|
|
85
|
+
if (options.workspace) {
|
|
86
|
+
headers['X-Workspace-Id'] = this.requireWorkspace();
|
|
87
|
+
}
|
|
88
|
+
let body;
|
|
89
|
+
if (options.rawBody !== undefined) {
|
|
90
|
+
body = options.rawBody;
|
|
91
|
+
if (options.contentType)
|
|
92
|
+
headers['Content-Type'] = options.contentType;
|
|
93
|
+
}
|
|
94
|
+
else if (options.body !== undefined) {
|
|
95
|
+
headers['Content-Type'] = 'application/json';
|
|
96
|
+
body = JSON.stringify(options.body);
|
|
97
|
+
}
|
|
98
|
+
let response;
|
|
99
|
+
try {
|
|
100
|
+
response = await this.fetchWithTimeout(url.toString(), { method, headers, body });
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
if (err instanceof DunSocialError)
|
|
104
|
+
throw err;
|
|
105
|
+
throw new DunSocialError(`Network error: ${err.message}`, {
|
|
106
|
+
code: 'network',
|
|
107
|
+
details: { cause: String(err) }
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const text = await response.text();
|
|
111
|
+
let parsed = null;
|
|
112
|
+
if (text) {
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(text);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
parsed = null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const details = (parsed ?? { status: response.status, body: text.slice(0, 300) });
|
|
122
|
+
const code = inferCode(response.status, parsed);
|
|
123
|
+
const retryAfterMs = retryAfterMsFrom(response);
|
|
124
|
+
if (this.shouldRetry(response.status, parsed, attempt)) {
|
|
125
|
+
await sleep(retryAfterMs ?? (attempt === 0 ? 400 : 800));
|
|
126
|
+
return this.request(options, attempt + 1);
|
|
127
|
+
}
|
|
128
|
+
throw new DunSocialError(parsed?.error || parsed?.message || (text ? text.slice(0, 300) : `HTTP ${response.status}`), {
|
|
129
|
+
status: response.status,
|
|
130
|
+
code,
|
|
131
|
+
details,
|
|
132
|
+
billingUrl: billingUrlFrom(parsed),
|
|
133
|
+
retryAfterMs
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (parsed && typeof parsed === 'object' && 'success' in parsed) {
|
|
137
|
+
if (parsed.success === false) {
|
|
138
|
+
throw new DunSocialError(parsed.error || parsed.message || 'Request failed', {
|
|
139
|
+
status: response.status,
|
|
140
|
+
code: 'validation',
|
|
141
|
+
details: parsed
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return parsed.data;
|
|
145
|
+
}
|
|
146
|
+
return parsed ?? undefined;
|
|
147
|
+
}
|
|
148
|
+
shouldRetry(status, parsed, attempt) {
|
|
149
|
+
if (attempt >= 1)
|
|
150
|
+
return false;
|
|
151
|
+
if (status >= 500)
|
|
152
|
+
return true;
|
|
153
|
+
if (status !== 429)
|
|
154
|
+
return false;
|
|
155
|
+
return !isXCapError(asRecord(parsed));
|
|
156
|
+
}
|
|
157
|
+
async fetchWithTimeout(url, init) {
|
|
158
|
+
const controller = new AbortController();
|
|
159
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
160
|
+
try {
|
|
161
|
+
return await this.fetchImpl(url, { ...init, signal: controller.signal });
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
if (err.name === 'AbortError') {
|
|
165
|
+
throw new DunSocialError(`Request timed out after ${this.timeoutMs}ms`, {
|
|
166
|
+
code: 'timeout'
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function env(name) {
|
|
177
|
+
try {
|
|
178
|
+
const value = globalThis.process
|
|
179
|
+
?.env?.[name];
|
|
180
|
+
return value && value.length > 0 ? value : undefined;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function sleep(ms) {
|
|
187
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
188
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type DunSocialErrorCode = 'missing_config' | 'unauthorized' | 'forbidden' | 'not_found' | 'validation' | 'subscription_required' | 'rate_limited' | 'x_cap_exceeded' | 'network' | 'timeout' | 'webhook_signature' | 'webhook_timestamp' | string;
|
|
2
|
+
export declare class DunSocialError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly code: DunSocialErrorCode;
|
|
5
|
+
readonly details?: unknown;
|
|
6
|
+
readonly billingUrl?: string;
|
|
7
|
+
readonly retryAfterMs?: number;
|
|
8
|
+
constructor(message: string, options?: {
|
|
9
|
+
status?: number;
|
|
10
|
+
code?: DunSocialErrorCode;
|
|
11
|
+
details?: unknown;
|
|
12
|
+
billingUrl?: string;
|
|
13
|
+
retryAfterMs?: number;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export declare function inferCode(status: number, details?: unknown): DunSocialErrorCode;
|
|
17
|
+
export declare function isXCapError(body: Record<string, unknown> | null): boolean;
|
|
18
|
+
export declare function asRecord(value: unknown): Record<string, unknown> | null;
|
|
19
|
+
export declare function billingUrlFrom(details: unknown): string | undefined;
|
|
20
|
+
export declare function retryAfterMsFrom(response: Response): number | undefined;
|
|
21
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAC3B,gBAAgB,GAChB,cAAc,GACd,WAAW,GACX,WAAW,GACX,YAAY,GACZ,uBAAuB,GACvB,cAAc,GACd,gBAAgB,GAChB,SAAS,GACT,SAAS,GACT,mBAAmB,GACnB,mBAAmB,GACnB,MAAM,CAAC;AAEV,qBAAa,cAAe,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAG9B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,kBAAkB,CAAC;QAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;KACjB;CAUP;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAgB/E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,OAAO,CAKzE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAKvE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGnE;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAQvE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export class DunSocialError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
billingUrl;
|
|
6
|
+
retryAfterMs;
|
|
7
|
+
constructor(message, options = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'DunSocialError';
|
|
10
|
+
this.status = options.status ?? 0;
|
|
11
|
+
this.code = options.code ?? inferCode(options.status ?? 0, options.details);
|
|
12
|
+
this.details = options.details;
|
|
13
|
+
this.billingUrl = options.billingUrl;
|
|
14
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function inferCode(status, details) {
|
|
18
|
+
const body = asRecord(details);
|
|
19
|
+
const explicit = typeof body?.code === 'string' ? body.code : undefined;
|
|
20
|
+
if (explicit === 'subscription_required')
|
|
21
|
+
return 'subscription_required';
|
|
22
|
+
if (explicit === 'x_cap_exceeded')
|
|
23
|
+
return 'x_cap_exceeded';
|
|
24
|
+
if (status === 401)
|
|
25
|
+
return 'unauthorized';
|
|
26
|
+
if (status === 402)
|
|
27
|
+
return 'subscription_required';
|
|
28
|
+
if (status === 403)
|
|
29
|
+
return 'forbidden';
|
|
30
|
+
if (status === 404)
|
|
31
|
+
return 'not_found';
|
|
32
|
+
if (status === 429) {
|
|
33
|
+
if (isXCapError(body))
|
|
34
|
+
return 'x_cap_exceeded';
|
|
35
|
+
return 'rate_limited';
|
|
36
|
+
}
|
|
37
|
+
if (status >= 400 && status < 500)
|
|
38
|
+
return 'validation';
|
|
39
|
+
if (status >= 500)
|
|
40
|
+
return 'network';
|
|
41
|
+
return explicit ?? 'network';
|
|
42
|
+
}
|
|
43
|
+
export function isXCapError(body) {
|
|
44
|
+
if (!body)
|
|
45
|
+
return false;
|
|
46
|
+
if (body.code === 'x_cap_exceeded')
|
|
47
|
+
return true;
|
|
48
|
+
const error = typeof body.error === 'string' ? body.error : '';
|
|
49
|
+
return /x monthly posting cap/i.test(error);
|
|
50
|
+
}
|
|
51
|
+
export function asRecord(value) {
|
|
52
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
export function billingUrlFrom(details) {
|
|
58
|
+
const body = asRecord(details);
|
|
59
|
+
return typeof body?.billingUrl === 'string' ? body.billingUrl : undefined;
|
|
60
|
+
}
|
|
61
|
+
export function retryAfterMsFrom(response) {
|
|
62
|
+
const header = response.headers.get('Retry-After');
|
|
63
|
+
if (!header)
|
|
64
|
+
return undefined;
|
|
65
|
+
const seconds = Number(header);
|
|
66
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
67
|
+
return Math.round(seconds * 1000);
|
|
68
|
+
const date = Date.parse(header);
|
|
69
|
+
if (!Number.isNaN(date))
|
|
70
|
+
return Math.max(0, date - Date.now());
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Accounts } from './resources/accounts';
|
|
2
|
+
import { Drafts } from './resources/drafts';
|
|
3
|
+
import { Media } from './resources/media';
|
|
4
|
+
import { Memory } from './resources/memory';
|
|
5
|
+
import { PersonalizationResource } from './resources/personalization';
|
|
6
|
+
import { Posts } from './resources/posts';
|
|
7
|
+
import { Workspaces } from './resources/workspaces';
|
|
8
|
+
import type { DunSocialOptions } from './types';
|
|
9
|
+
import { constructEvent } from './webhooks';
|
|
10
|
+
export declare class DunSocial {
|
|
11
|
+
readonly workspaces: Workspaces;
|
|
12
|
+
readonly accounts: Accounts;
|
|
13
|
+
readonly posts: Posts;
|
|
14
|
+
readonly drafts: Drafts;
|
|
15
|
+
readonly media: Media;
|
|
16
|
+
readonly memory: Memory;
|
|
17
|
+
readonly personalization: PersonalizationResource;
|
|
18
|
+
static webhooks: {
|
|
19
|
+
constructEvent: typeof constructEvent;
|
|
20
|
+
};
|
|
21
|
+
constructor(options?: DunSocialOptions);
|
|
22
|
+
}
|
|
23
|
+
export { DunSocialError } from './errors';
|
|
24
|
+
export { constructEvent } from './webhooks';
|
|
25
|
+
export { SDK_VERSION } from './version';
|
|
26
|
+
export type { components, paths } from './generated/openapi';
|
|
27
|
+
export type { ConstructEventOptions, CreateCollectionInput, CreateDraftInput, DunSocialOptions, InstagramPostOptions, MediaAsset, Paginated, Personalization, PinterestPostOptions, Post, PostMetadata, PostStatus, PublishPostInput, RedditPostOptions, SchedulePostInput, SocialAccount, TikTokPostOptions, UploadInput, ValidatePostInput, ValidatePostResult, WebhookEvent, WebhookEventType, WebhookHeaders, Workspace } from './types';
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,qBAAa,SAAS;IACrB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,EAAE,uBAAuB,CAAC;IAElD,MAAM,CAAC,QAAQ;;MAAsB;gBAEzB,OAAO,GAAE,gBAAqB;CAU1C;AAED,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC7D,YAAY,EACX,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,UAAU,EACV,SAAS,EACT,eAAe,EACf,oBAAoB,EACpB,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,SAAS,EACT,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { HttpClient } from './client';
|
|
2
|
+
import { Accounts } from './resources/accounts';
|
|
3
|
+
import { Drafts } from './resources/drafts';
|
|
4
|
+
import { Media } from './resources/media';
|
|
5
|
+
import { Memory } from './resources/memory';
|
|
6
|
+
import { PersonalizationResource } from './resources/personalization';
|
|
7
|
+
import { Posts } from './resources/posts';
|
|
8
|
+
import { Workspaces } from './resources/workspaces';
|
|
9
|
+
import { constructEvent } from './webhooks';
|
|
10
|
+
export class DunSocial {
|
|
11
|
+
workspaces;
|
|
12
|
+
accounts;
|
|
13
|
+
posts;
|
|
14
|
+
drafts;
|
|
15
|
+
media;
|
|
16
|
+
memory;
|
|
17
|
+
personalization;
|
|
18
|
+
static webhooks = { constructEvent };
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
const http = new HttpClient(options);
|
|
21
|
+
this.workspaces = new Workspaces(http);
|
|
22
|
+
this.accounts = new Accounts(http);
|
|
23
|
+
this.posts = new Posts(http);
|
|
24
|
+
this.drafts = new Drafts(http);
|
|
25
|
+
this.media = new Media(http);
|
|
26
|
+
this.memory = new Memory(http);
|
|
27
|
+
this.personalization = new PersonalizationResource(http);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export { DunSocialError } from './errors';
|
|
31
|
+
export { constructEvent } from './webhooks';
|
|
32
|
+
export { SDK_VERSION } from './version';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const MAX_IMAGE_SIZE: number;
|
|
2
|
+
export declare const MAX_VIDEO_SIZE: number;
|
|
3
|
+
export declare const ALLOWED_IMAGE_TYPES: readonly ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
4
|
+
export declare const ALLOWED_VIDEO_TYPES: readonly ["video/mp4", "video/quicktime", "video/webm"];
|
|
5
|
+
export declare function byteLength(file: Uint8Array | ArrayBuffer | Blob): number;
|
|
6
|
+
export declare function toBytes(file: Uint8Array | ArrayBuffer | Blob): Uint8Array | Blob;
|
|
7
|
+
export declare function preflightMediaUpload(input: {
|
|
8
|
+
mimeType: string;
|
|
9
|
+
fileSize: number;
|
|
10
|
+
}): {
|
|
11
|
+
ok: true;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=media-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-limits.d.ts","sourceRoot":"","sources":["../src/media-limits.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,QAAkB,CAAC;AAC9C,eAAO,MAAM,cAAc,QAAoB,CAAC;AAEhD,eAAO,MAAM,mBAAmB,iEAKtB,CAAC;AAEX,eAAO,MAAM,mBAAmB,yDAA0D,CAAC;AAE3F,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI,GAAG,MAAM,CAIxE;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI,GAAG,UAAU,GAAG,IAAI,CAIhF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA2BhD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const MAX_IMAGE_SIZE = 8 * 1024 * 1024;
|
|
2
|
+
export const MAX_VIDEO_SIZE = 512 * 1024 * 1024;
|
|
3
|
+
export const ALLOWED_IMAGE_TYPES = [
|
|
4
|
+
'image/jpeg',
|
|
5
|
+
'image/png',
|
|
6
|
+
'image/gif',
|
|
7
|
+
'image/webp'
|
|
8
|
+
];
|
|
9
|
+
export const ALLOWED_VIDEO_TYPES = ['video/mp4', 'video/quicktime', 'video/webm'];
|
|
10
|
+
export function byteLength(file) {
|
|
11
|
+
if (file instanceof Blob)
|
|
12
|
+
return file.size;
|
|
13
|
+
if (file instanceof ArrayBuffer)
|
|
14
|
+
return file.byteLength;
|
|
15
|
+
return file.byteLength;
|
|
16
|
+
}
|
|
17
|
+
export function toBytes(file) {
|
|
18
|
+
if (file instanceof Blob)
|
|
19
|
+
return file;
|
|
20
|
+
if (file instanceof ArrayBuffer)
|
|
21
|
+
return new Uint8Array(file);
|
|
22
|
+
return file;
|
|
23
|
+
}
|
|
24
|
+
export function preflightMediaUpload(input) {
|
|
25
|
+
const { mimeType, fileSize } = input;
|
|
26
|
+
const isImage = ALLOWED_IMAGE_TYPES.includes(mimeType);
|
|
27
|
+
const isVideo = ALLOWED_VIDEO_TYPES.includes(mimeType);
|
|
28
|
+
if (!isImage && !isVideo) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
message: `Unsupported MIME type "${mimeType}". ` +
|
|
32
|
+
`Allowed images: ${ALLOWED_IMAGE_TYPES.join(', ')}; ` +
|
|
33
|
+
`videos: ${ALLOWED_VIDEO_TYPES.join(', ')}`
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (isVideo && fileSize > MAX_VIDEO_SIZE) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
message: `Video files must be less than ${MAX_VIDEO_SIZE / 1024 / 1024}MB`
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (isImage && fileSize > MAX_IMAGE_SIZE) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
message: `Image files must be less than ${MAX_IMAGE_SIZE / 1024 / 1024}MB`
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { ok: true };
|
|
49
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Paginated } from './types';
|
|
2
|
+
export declare function asPage<T>(data: unknown): Paginated<T>;
|
|
3
|
+
export declare function iteratePages<T>(fetchPage: (offset: number, limit: number) => Promise<Paginated<T>>, pageSize?: number): AsyncGenerator<T, void, undefined>;
|
|
4
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,wBAAgB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,CAuBrD;AAED,wBAAuB,YAAY,CAAC,CAAC,EACpC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EACnE,QAAQ,SAAK,GACX,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,CAQpC"}
|