@dyqr/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.
@@ -0,0 +1,40 @@
1
+ import { type DeepLinkTarget } from './deep-link.js';
2
+ import type { CreateLinkInput, CreateLinkResult, GetLinkResult, ListLinksParams, ListLinksResult, QrFormat, QrImage, UpdateLinkInput } from './types.js';
3
+ export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
4
+ export type DyqrClientOptions = {
5
+ baseUrl: string;
6
+ token: string;
7
+ fetch?: FetchLike;
8
+ userAgent?: string;
9
+ dashboardUrl?: string;
10
+ };
11
+ export declare class DyqrClient {
12
+ private readonly baseUrl;
13
+ private readonly token;
14
+ private readonly fetchImpl;
15
+ private readonly userAgent;
16
+ private readonly dashboardUrl;
17
+ readonly links: LinksApi;
18
+ constructor(options: DyqrClientOptions);
19
+ deepLink(target: DeepLinkTarget): string;
20
+ requestJson<T>(method: string, path: string, body?: unknown): Promise<T>;
21
+ requestBinary(method: string, path: string): Promise<{
22
+ contentType: string;
23
+ bytes: Uint8Array;
24
+ }>;
25
+ private send;
26
+ private toError;
27
+ }
28
+ declare class LinksApi {
29
+ private readonly client;
30
+ constructor(client: DyqrClient);
31
+ create(input: CreateLinkInput): Promise<CreateLinkResult>;
32
+ list(params?: ListLinksParams): Promise<ListLinksResult>;
33
+ get(alias: string): Promise<GetLinkResult>;
34
+ update(alias: string, patch: UpdateLinkInput): Promise<GetLinkResult>;
35
+ remove(alias: string): Promise<void>;
36
+ qr(alias: string, options?: {
37
+ format?: QrFormat;
38
+ }): Promise<QrImage>;
39
+ }
40
+ export {};
package/dist/client.js ADDED
@@ -0,0 +1,106 @@
1
+ import { buildDashboardDeepLink } from './deep-link.js';
2
+ import { DyqrApiError } from './errors.js';
3
+ export class DyqrClient {
4
+ baseUrl;
5
+ token;
6
+ fetchImpl;
7
+ userAgent;
8
+ dashboardUrl;
9
+ links;
10
+ constructor(options) {
11
+ this.baseUrl = options.baseUrl.replace(/\/+$/, '');
12
+ this.token = options.token;
13
+ this.fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
14
+ this.userAgent = options.userAgent;
15
+ this.dashboardUrl = (options.dashboardUrl ?? options.baseUrl).replace(/\/+$/, '');
16
+ this.links = new LinksApi(this);
17
+ }
18
+ // 生成后台 deep link(campaign / 数据分析 / 高级设置等交给用户在网页里完成)。
19
+ deepLink(target) {
20
+ return buildDashboardDeepLink(this.dashboardUrl, target);
21
+ }
22
+ // 发起请求并解析 JSON;非 2xx 或 { ok:false } 时抛 DyqrApiError。
23
+ async requestJson(method, path, body) {
24
+ const response = await this.send(method, path, body);
25
+ const data = (await response.json().catch(() => null));
26
+ if (!response.ok || (data && typeof data === 'object' && 'ok' in data && data.ok === false)) {
27
+ throw this.toError(response.status, data);
28
+ }
29
+ return data;
30
+ }
31
+ // 获取二进制响应(如二维码图片)。
32
+ async requestBinary(method, path) {
33
+ const response = await this.send(method, path);
34
+ if (!response.ok) {
35
+ const data = (await response.json().catch(() => null));
36
+ throw this.toError(response.status, data);
37
+ }
38
+ const buffer = await response.arrayBuffer();
39
+ return {
40
+ contentType: response.headers.get('content-type') ?? 'application/octet-stream',
41
+ bytes: new Uint8Array(buffer),
42
+ };
43
+ }
44
+ async send(method, path, body) {
45
+ const headers = { authorization: `Bearer ${this.token}` };
46
+ if (this.userAgent) {
47
+ headers['user-agent'] = this.userAgent;
48
+ }
49
+ if (body !== undefined) {
50
+ headers['content-type'] = 'application/json';
51
+ }
52
+ return this.fetchImpl(`${this.baseUrl}${path}`, {
53
+ method,
54
+ headers,
55
+ body: body !== undefined ? JSON.stringify(body) : undefined,
56
+ });
57
+ }
58
+ toError(status, data) {
59
+ const message = data?.message || data?.error_description || data?.error || `Request failed with status ${status}`;
60
+ return new DyqrApiError(message, status);
61
+ }
62
+ }
63
+ class LinksApi {
64
+ client;
65
+ constructor(client) {
66
+ this.client = client;
67
+ }
68
+ // 创建短链(动态二维码默认指向该短链)。
69
+ create(input) {
70
+ return this.client.requestJson('POST', '/api/links', input);
71
+ }
72
+ // 分页列出当前账号的短链。
73
+ list(params = {}) {
74
+ const query = new URLSearchParams();
75
+ if (params.page)
76
+ query.set('page', String(params.page));
77
+ if (params.pageSize)
78
+ query.set('pageSize', String(params.pageSize));
79
+ if (params.campaign)
80
+ query.set('campaign', params.campaign);
81
+ if (params.tag)
82
+ query.set('tag', params.tag);
83
+ if (params.sortBy)
84
+ query.set('sortBy', params.sortBy);
85
+ if (params.sortOrder)
86
+ query.set('sortOrder', params.sortOrder);
87
+ const qs = query.toString();
88
+ return this.client.requestJson('GET', `/api/links${qs ? `?${qs}` : ''}`);
89
+ }
90
+ get(alias) {
91
+ return this.client.requestJson('GET', `/api/links/${encodeURIComponent(alias)}`);
92
+ }
93
+ // 更新短链(改目标 URL、标题、启用/停用、二维码样式等)。
94
+ update(alias, patch) {
95
+ return this.client.requestJson('PATCH', `/api/links/${encodeURIComponent(alias)}`, patch);
96
+ }
97
+ async remove(alias) {
98
+ await this.client.requestJson('DELETE', `/api/links/${encodeURIComponent(alias)}`);
99
+ }
100
+ // 导出二维码图片(默认 SVG,PNG 为 best-effort)。
101
+ async qr(alias, options = {}) {
102
+ const format = options.format === 'png' ? 'png' : 'svg';
103
+ const { contentType, bytes } = await this.client.requestBinary('GET', `/api/links/${encodeURIComponent(alias)}/qr?format=${format}`);
104
+ return { format, contentType, bytes };
105
+ }
106
+ }
@@ -0,0 +1,10 @@
1
+ export type DeepLinkTarget = {
2
+ page: 'links' | 'campaigns' | 'account' | 'connections';
3
+ } | {
4
+ page: 'link' | 'linkStats';
5
+ alias: string;
6
+ } | {
7
+ page: 'campaign' | 'campaignStats';
8
+ campaignId: string;
9
+ };
10
+ export declare function buildDashboardDeepLink(dashboardUrl: string, target: DeepLinkTarget): string;
@@ -0,0 +1,21 @@
1
+ export function buildDashboardDeepLink(dashboardUrl, target) {
2
+ const base = dashboardUrl.replace(/\/+$/, '');
3
+ switch (target.page) {
4
+ case 'links':
5
+ return `${base}/links`;
6
+ case 'link':
7
+ return `${base}/links/${encodeURIComponent(target.alias)}`;
8
+ case 'linkStats':
9
+ return `${base}/links/${encodeURIComponent(target.alias)}/stats`;
10
+ case 'campaigns':
11
+ return `${base}/campaigns`;
12
+ case 'campaign':
13
+ return `${base}/campaigns/${encodeURIComponent(target.campaignId)}`;
14
+ case 'campaignStats':
15
+ return `${base}/campaigns/${encodeURIComponent(target.campaignId)}/stats`;
16
+ case 'account':
17
+ return `${base}/account`;
18
+ case 'connections':
19
+ return `${base}/account/connections`;
20
+ }
21
+ }
@@ -0,0 +1,4 @@
1
+ export declare class DyqrApiError extends Error {
2
+ readonly status: number;
3
+ constructor(message: string, status: number);
4
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,9 @@
1
+ // API 调用失败时抛出的统一错误,携带 HTTP 状态码与服务端返回的信息。
2
+ export class DyqrApiError extends Error {
3
+ status;
4
+ constructor(message, status) {
5
+ super(message);
6
+ this.name = 'DyqrApiError';
7
+ this.status = status;
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ export type { DyqrClientOptions, FetchLike } from './client.js';
2
+ export { DyqrClient } from './client.js';
3
+ export type { DeepLinkTarget } from './deep-link.js';
4
+ export { buildDashboardDeepLink } from './deep-link.js';
5
+ export { DyqrApiError } from './errors.js';
6
+ export type { CreateLinkInput, CreateLinkResult, DyqrLink, DyqrLinkListItem, GetLinkResult, LinkRedirectMode, ListLinksParams, ListLinksResult, QrFormat, QrImage, QrStyleInput, UpdateLinkInput, } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { DyqrClient } from './client.js';
2
+ export { buildDashboardDeepLink } from './deep-link.js';
3
+ export { DyqrApiError } from './errors.js';
@@ -0,0 +1,90 @@
1
+ export type DyqrLink = {
2
+ alias: string;
3
+ title: string;
4
+ targetUrl: string;
5
+ baseTargetUrl: string | null;
6
+ campaignId: string | null;
7
+ createdAt: string;
8
+ updatedAt: string;
9
+ isDisabled: boolean;
10
+ aliasKind: 'auto' | 'custom';
11
+ redirectMode: 'auto' | 'direct' | 'interstitial';
12
+ qrStyleSource: 'own' | 'campaign';
13
+ expiresAt: string | null;
14
+ maxClicks: number | null;
15
+ hasPassword: boolean;
16
+ };
17
+ export type DyqrLinkListItem = DyqrLink & {
18
+ shortUrl: string;
19
+ tags: string[];
20
+ last7DaysClicks: number;
21
+ last7DaysSeries: number[];
22
+ };
23
+ export type QrStyleInput = {
24
+ size?: number;
25
+ foreground?: string;
26
+ background?: string;
27
+ isTransparent?: boolean;
28
+ errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';
29
+ moduleShape?: 'square' | 'dot';
30
+ dotScale?: number;
31
+ logoDataUrl?: string | null;
32
+ logoScale?: number;
33
+ watermarkEnabled?: boolean;
34
+ eyes?: Record<string, unknown>;
35
+ frame?: Record<string, unknown>;
36
+ };
37
+ export type LinkRedirectMode = 'auto' | 'direct' | 'interstitial';
38
+ export type CreateLinkInput = {
39
+ targetUrl: string;
40
+ alias?: string;
41
+ title?: string;
42
+ tags?: string[];
43
+ redirectMode?: LinkRedirectMode;
44
+ expiresAt?: string | null;
45
+ maxClicks?: number | null;
46
+ password?: string;
47
+ campaignId?: string | null;
48
+ qrStyle?: QrStyleInput;
49
+ qrStyleSource?: 'own' | 'campaign';
50
+ };
51
+ export type UpdateLinkInput = {
52
+ title?: string;
53
+ targetUrl?: string;
54
+ tags?: string[];
55
+ redirectMode?: LinkRedirectMode;
56
+ expiresAt?: string | null;
57
+ maxClicks?: number | null;
58
+ campaignId?: string | null;
59
+ qrStyle?: QrStyleInput;
60
+ qrStyleSource?: 'own' | 'campaign';
61
+ isDisabled?: boolean;
62
+ };
63
+ export type ListLinksParams = {
64
+ page?: number;
65
+ pageSize?: number;
66
+ campaign?: string;
67
+ tag?: string;
68
+ sortBy?: 'createdAt' | 'alias' | 'title';
69
+ sortOrder?: 'asc' | 'desc';
70
+ };
71
+ export type ListLinksResult = {
72
+ links: DyqrLinkListItem[];
73
+ page: number;
74
+ pageSize: number;
75
+ totalCount: number;
76
+ };
77
+ export type CreateLinkResult = {
78
+ link: DyqrLink;
79
+ shortUrl: string;
80
+ };
81
+ export type GetLinkResult = {
82
+ link: DyqrLink;
83
+ shortUrl: string;
84
+ };
85
+ export type QrFormat = 'svg' | 'png';
86
+ export type QrImage = {
87
+ format: QrFormat;
88
+ contentType: string;
89
+ bytes: Uint8Array;
90
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@dyqr/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Typed client for the DYQR.me short-link & dynamic QR code API.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "license": "MIT",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "devDependencies": {
23
+ "typescript": "^5.9.3"
24
+ },
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "typecheck": "tsc --noEmit -p tsconfig.json"
28
+ }
29
+ }