@stackshift-cloud/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/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # @stackshift-cloud/sdk
2
+
3
+ Official JavaScript and TypeScript SDK for StackShift.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @stackshift-cloud/sdk
9
+ ```
10
+
11
+ ## Upload an asset
12
+
13
+ ```ts
14
+ import { StackShift } from '@stackshift-cloud/sdk'
15
+ import { readFile } from 'node:fs/promises'
16
+
17
+ const stackshift = new StackShift()
18
+
19
+ const file = new Blob([await readFile('./avatar.png')], { type: 'image/png' })
20
+
21
+ const asset = await stackshift.assets.upload(file, {
22
+ folder: 'avatars',
23
+ visibility: 'public',
24
+ metadata: { userId: 'user_123' },
25
+ })
26
+
27
+ console.log(asset.url)
28
+ ```
29
+
30
+ You do not pass a deployed StackShift project ID. The API key identifies the StackShift account, and StackShift resolves the default asset space internally.
31
+
32
+ ## Private assets
33
+
34
+ ```ts
35
+ const asset = await stackshift.assets.upload(file, {
36
+ bucket: 'imports',
37
+ key: 'customers.csv',
38
+ visibility: 'private',
39
+ })
40
+
41
+ const signed = await stackshift.assets.signedUrl(asset.id, {
42
+ expiresIn: '10m',
43
+ maxDownloads: 1,
44
+ })
45
+
46
+ console.log(signed.url)
47
+ ```
48
+
49
+ ## Direct browser uploads
50
+
51
+ Create a signed upload URL on your server:
52
+
53
+ ```ts
54
+ const upload = await stackshift.assets.createUploadSession({
55
+ bucket: 'avatars',
56
+ key: 'users/user_123.png',
57
+ visibility: 'public',
58
+ expiresIn: '10m',
59
+ maxBytes: 5_000_000,
60
+ })
61
+ ```
62
+
63
+ Then upload from the browser:
64
+
65
+ ```ts
66
+ await fetch(upload.url, {
67
+ method: upload.method,
68
+ body: file,
69
+ headers: { 'Content-Type': file.type },
70
+ })
71
+ ```
72
+
73
+ StackShift chooses storage nodes, writes to StackShift-owned disks, replicates internally, and returns CDN-ready asset URLs. SDK users never choose storage nodes and never receive disk paths.
@@ -0,0 +1,16 @@
1
+ import type { StackShift } from './client';
2
+ import type { Asset, AssetListOptions, AssetListResult, AssetSignedUploadUrl, AssetSignedUploadUrlOptions, AssetSignedUrl, AssetSignedUrlOptions, AssetUploadOptions } from './types';
3
+ export declare class AssetsClient {
4
+ private readonly client;
5
+ constructor(client: StackShift);
6
+ upload(file: Blob, options?: AssetUploadOptions): Promise<Asset>;
7
+ list(options?: AssetListOptions): Promise<AssetListResult>;
8
+ get(assetId: string): Promise<Asset>;
9
+ delete(assetId: string): Promise<{
10
+ deleted: boolean;
11
+ }>;
12
+ replace(assetId: string, file: Blob, options?: Pick<AssetUploadOptions, 'cacheControl' | 'metadata'>): Promise<Asset>;
13
+ signedUrl(assetId: string, options: AssetSignedUrlOptions): Promise<AssetSignedUrl>;
14
+ signedUploadUrl(options?: AssetSignedUploadUrlOptions): Promise<AssetSignedUploadUrl>;
15
+ createUploadSession(options?: AssetSignedUploadUrlOptions): Promise<AssetSignedUploadUrl>;
16
+ }
package/dist/assets.js ADDED
@@ -0,0 +1,81 @@
1
+ export class AssetsClient {
2
+ client;
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ upload(file, options = {}) {
7
+ return this.client.request('/assets/upload', {
8
+ method: 'POST',
9
+ body: assetForm(file, options),
10
+ });
11
+ }
12
+ list(options = {}) {
13
+ const query = new URLSearchParams();
14
+ append(query, 'bucket', options.bucket);
15
+ append(query, 'prefix', options.prefix);
16
+ append(query, 'visibility', options.visibility);
17
+ append(query, 'limit', options.limit);
18
+ append(query, 'cursor', options.cursor);
19
+ const suffix = query.toString();
20
+ return this.client.request(`/assets${suffix ? `?${suffix}` : ''}`);
21
+ }
22
+ get(assetId) {
23
+ return this.client.request(`/assets/${encodeURIComponent(assetId)}`);
24
+ }
25
+ delete(assetId) {
26
+ return this.client.request(`/assets/${encodeURIComponent(assetId)}`, {
27
+ method: 'DELETE',
28
+ });
29
+ }
30
+ replace(assetId, file, options = {}) {
31
+ return this.client.request(`/assets/${encodeURIComponent(assetId)}/replace`, {
32
+ method: 'PUT',
33
+ body: assetForm(file, options),
34
+ });
35
+ }
36
+ signedUrl(assetId, options) {
37
+ return this.client.request(`/assets/${encodeURIComponent(assetId)}/signed-url`, {
38
+ method: 'POST',
39
+ body: {
40
+ expiresIn: options.expiresIn,
41
+ maxDownloads: options.maxDownloads,
42
+ },
43
+ });
44
+ }
45
+ signedUploadUrl(options = {}) {
46
+ return this.createUploadSession(options);
47
+ }
48
+ createUploadSession(options = {}) {
49
+ return this.client.request('/assets/upload-sessions', {
50
+ method: 'POST',
51
+ body: {
52
+ bucket: options.bucket,
53
+ key: options.key,
54
+ folder: options.folder,
55
+ visibility: options.visibility,
56
+ cacheControl: options.cacheControl,
57
+ metadata: options.metadata,
58
+ expiresIn: options.expiresIn,
59
+ maxBytes: options.maxBytes,
60
+ },
61
+ });
62
+ }
63
+ }
64
+ function assetForm(file, options) {
65
+ const body = new FormData();
66
+ body.append('file', file);
67
+ append(body, 'bucket', options.bucket);
68
+ append(body, 'key', options.key);
69
+ append(body, 'folder', options.folder);
70
+ append(body, 'visibility', options.visibility);
71
+ append(body, 'cacheControl', options.cacheControl);
72
+ if (options.metadata) {
73
+ body.append('metadata', JSON.stringify(options.metadata));
74
+ }
75
+ return body;
76
+ }
77
+ function append(target, key, value) {
78
+ if (value !== undefined && value !== null && value !== '') {
79
+ target.append(key, String(value));
80
+ }
81
+ }
@@ -0,0 +1,16 @@
1
+ import { AssetsClient } from './assets';
2
+ import { ProjectsClient } from './projects';
3
+ import type { StackShiftOptions } from './types';
4
+ export declare class StackShift {
5
+ readonly apiKey: string;
6
+ readonly baseUrl: string;
7
+ readonly projects: ProjectsClient;
8
+ readonly assets: AssetsClient;
9
+ private readonly fetcher;
10
+ constructor(options?: StackShiftOptions);
11
+ request<T>(path: string, init?: {
12
+ method?: string;
13
+ body?: unknown;
14
+ headers?: Record<string, string>;
15
+ }): Promise<T>;
16
+ }
package/dist/client.js ADDED
@@ -0,0 +1,57 @@
1
+ import { AssetsClient } from './assets';
2
+ import { ProjectsClient } from './projects';
3
+ const defaultBaseUrl = 'https://api.stackshift.cloud/api/v1';
4
+ export class StackShift {
5
+ apiKey;
6
+ baseUrl;
7
+ projects;
8
+ assets;
9
+ fetcher;
10
+ constructor(options = {}) {
11
+ const apiKey = options.apiKey ?? env('STACKSHIFT_API_KEY');
12
+ if (!apiKey) {
13
+ throw new Error('StackShift SDK requires an apiKey');
14
+ }
15
+ this.apiKey = apiKey;
16
+ this.baseUrl = (options.baseUrl ?? defaultBaseUrl).replace(/\/$/, '');
17
+ this.fetcher = options.fetch ?? fetch;
18
+ this.projects = new ProjectsClient(this);
19
+ this.assets = new AssetsClient(this);
20
+ }
21
+ async request(path, init = {}) {
22
+ const isFormData = typeof FormData !== 'undefined' && init.body instanceof FormData;
23
+ const response = await this.fetcher(`${this.baseUrl}${path}`, {
24
+ method: init.method ?? 'GET',
25
+ headers: stripUndefined({
26
+ Authorization: `Bearer ${this.apiKey}`,
27
+ 'Content-Type': isFormData ? undefined : 'application/json',
28
+ ...init.headers,
29
+ }),
30
+ body: (init.body === undefined ? undefined : isFormData ? init.body : JSON.stringify(stripUndefined(init.body))),
31
+ });
32
+ const raw = await response.text();
33
+ const parsed = raw ? JSON.parse(raw) : null;
34
+ if (!response.ok) {
35
+ throw new Error(parsed?.message ?? parsed?.error ?? `StackShift API request failed with ${response.status}`);
36
+ }
37
+ if (parsed && typeof parsed === 'object' && 'success' in parsed && 'data' in parsed) {
38
+ return parsed.data;
39
+ }
40
+ return parsed;
41
+ }
42
+ }
43
+ function env(key) {
44
+ const maybeProcess = globalThis.process;
45
+ return maybeProcess?.env?.[key];
46
+ }
47
+ function stripUndefined(value) {
48
+ if (Array.isArray(value)) {
49
+ return value.map(stripUndefined);
50
+ }
51
+ if (value && typeof value === 'object') {
52
+ return Object.fromEntries(Object.entries(value)
53
+ .filter(([, item]) => item !== undefined)
54
+ .map(([key, item]) => [key, stripUndefined(item)]));
55
+ }
56
+ return value;
57
+ }
@@ -0,0 +1,4 @@
1
+ export { StackShift } from './client';
2
+ export { AssetsClient } from './assets';
3
+ export { ProjectsClient } from './projects';
4
+ export type { Asset, AssetListOptions, AssetListResult, AssetSignedUploadUrl, AssetSignedUploadUrlOptions, AssetSignedUrl, AssetSignedUrlOptions, AssetUploadOptions, AssetVisibility, JsonValue, Project, ProjectListOptions, StackShiftOptions, } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { StackShift } from './client';
2
+ export { AssetsClient } from './assets';
3
+ export { ProjectsClient } from './projects';
@@ -0,0 +1,7 @@
1
+ import type { StackShift } from './client';
2
+ import type { Project, ProjectListOptions } from './types';
3
+ export declare class ProjectsClient {
4
+ private readonly client;
5
+ constructor(client: StackShift);
6
+ list(options?: ProjectListOptions): Promise<Project[]>;
7
+ }
@@ -0,0 +1,18 @@
1
+ export class ProjectsClient {
2
+ client;
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ list(options = {}) {
7
+ const query = new URLSearchParams();
8
+ append(query, 'page', options.page);
9
+ append(query, 'per_page', options.perPage);
10
+ const suffix = query.toString();
11
+ return this.client.request(`/projects/${suffix ? `?${suffix}` : ''}`);
12
+ }
13
+ }
14
+ function append(target, key, value) {
15
+ if (value !== undefined && value !== null && value !== '') {
16
+ target.append(key, String(value));
17
+ }
18
+ }
@@ -0,0 +1,77 @@
1
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
2
+ [key: string]: JsonValue;
3
+ };
4
+ export type AssetVisibility = 'public' | 'private';
5
+ export interface StackShiftOptions {
6
+ apiKey?: string;
7
+ baseUrl?: string;
8
+ fetch?: typeof fetch;
9
+ }
10
+ export interface Project {
11
+ id: string;
12
+ name: string;
13
+ status?: string;
14
+ url?: string;
15
+ created_at?: string;
16
+ updated_at?: string;
17
+ }
18
+ export interface ProjectListOptions {
19
+ page?: number;
20
+ perPage?: number;
21
+ }
22
+ export interface Asset {
23
+ id: string;
24
+ bucket: string;
25
+ key: string;
26
+ original_name: string;
27
+ mime_type: string;
28
+ size: number;
29
+ checksum_sha256: string;
30
+ visibility: AssetVisibility;
31
+ status: 'uploading' | 'available' | 'deleted' | 'failed';
32
+ replication_status: 'pending' | 'replicated' | 'degraded' | 'failed';
33
+ metadata?: Record<string, JsonValue>;
34
+ cache_control?: string;
35
+ url?: string;
36
+ created_at: string;
37
+ updated_at: string;
38
+ }
39
+ export interface AssetUploadOptions {
40
+ bucket?: string;
41
+ key?: string;
42
+ folder?: string;
43
+ visibility?: AssetVisibility;
44
+ cacheControl?: string;
45
+ metadata?: Record<string, JsonValue>;
46
+ }
47
+ export interface AssetListOptions {
48
+ bucket?: string;
49
+ prefix?: string;
50
+ visibility?: AssetVisibility;
51
+ limit?: number;
52
+ cursor?: string;
53
+ }
54
+ export interface AssetListResult {
55
+ assets: Asset[];
56
+ total: number;
57
+ next_cursor?: string;
58
+ }
59
+ export interface AssetSignedUrlOptions {
60
+ expiresIn: string;
61
+ maxDownloads?: number;
62
+ }
63
+ export interface AssetSignedUrl {
64
+ url: string;
65
+ expires_at: string;
66
+ }
67
+ export interface AssetSignedUploadUrlOptions extends AssetUploadOptions {
68
+ expiresIn?: string;
69
+ maxBytes?: number;
70
+ }
71
+ export interface AssetSignedUploadUrl {
72
+ upload_session_id: string;
73
+ url: string;
74
+ method: 'PUT';
75
+ expires_at: string;
76
+ max_bytes: number;
77
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@stackshift-cloud/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official JavaScript and TypeScript SDK for StackShift.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json"
20
+ },
21
+ "devDependencies": {
22
+ "typescript": "^5.4.0"
23
+ }
24
+ }