From 194b28871efc592b6953a05cf8d497f8847c7a89 Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Mon, 13 Apr 2026 16:47:00 +0200 Subject: [PATCH 1/4] build(shared): add tsc compilation in build scripts --- package.json | 6 +----- packages/iam/package.json | 4 ++-- packages/keyv-tigris/package.json | 4 ++-- packages/react/package.json | 4 ++-- packages/storage/package.json | 4 ++-- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 131d22d..0205be3 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,7 @@ ], "scripts": { "build": "npm run build --workspaces --if-present", - "build:storage": "npm run build --workspace=@tigrisdata/storage", - "build:iam": "npm run build --workspace=@tigrisdata/iam", - "build:keyv-tigris": "npm run build --workspace=@tigrisdata/keyv-tigris", - "build:react": "npm run build --workspace=@tigrisdata/react", - "dev": "npm run dev --workspaces", + "dev": "npm run dev --workspaces --if-present", "publint": "npm run publint --workspaces --if-present", "lint": "npm run lint --workspaces --if-present", "lint:fix": "npm run lint:fix --workspaces --if-present", diff --git a/packages/iam/package.json b/packages/iam/package.json index acc646b..2719970 100644 --- a/packages/iam/package.json +++ b/packages/iam/package.json @@ -27,8 +27,8 @@ "access": "public" }, "scripts": { - "build": "tsup", - "dev": "tsup --watch", + "build": "tsc --noEmit && tsup", + "dev": "tsc --noEmit && tsup --watch", "publint": "publint", "lint": "eslint src/**/*.ts", "lint:fix": "eslint src/**/*.ts --fix", diff --git a/packages/keyv-tigris/package.json b/packages/keyv-tigris/package.json index a837a98..18a263a 100644 --- a/packages/keyv-tigris/package.json +++ b/packages/keyv-tigris/package.json @@ -27,8 +27,8 @@ "access": "public" }, "scripts": { - "build": "tsup", - "dev": "tsup --watch", + "build": "tsc --noEmit && tsup", + "dev": "tsc --noEmit && tsup --watch", "clean": "rm -rf dist", "test": "vitest run", "publint": "publint" diff --git a/packages/react/package.json b/packages/react/package.json index b04674f..279b88d 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -47,8 +47,8 @@ }, "homepage": "https://github.com/tigrisdata/storage/tree/main/packages/react#readme", "scripts": { - "build": "tsup", - "dev": "tsup --watch", + "build": "tsc --noEmit && tsup", + "dev": "tsc --noEmit && tsup --watch", "test": "vitest run --config vitest.config.ts", "prepublishOnly": "npm run build" }, diff --git a/packages/storage/package.json b/packages/storage/package.json index c109262..61de616 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -37,8 +37,8 @@ "access": "public" }, "scripts": { - "build": "tsup", - "dev": "tsup --watch", + "build": "tsc --noEmit && tsup", + "dev": "tsc --noEmit && tsup --watch", "publint": "publint", "lint": "eslint 'src/**/*.ts'", "lint:fix": "eslint 'src/**/*.ts' --fix", From 5b67ca75afb300a2e1bcd0e83730a2dc78a2b498 Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Mon, 13 Apr 2026 16:47:57 +0200 Subject: [PATCH 2/4] chore(storage): fix ts error --- packages/storage/src/lib/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/storage/src/lib/config.ts b/packages/storage/src/lib/config.ts index 30dd6bf..79b2839 100644 --- a/packages/storage/src/lib/config.ts +++ b/packages/storage/src/lib/config.ts @@ -5,7 +5,7 @@ import { } from '@shared/index'; import type { TigrisStorageConfig } from './types'; -const configMap: Record = { +const configMap: Partial> = { endpoint: 'TIGRIS_STORAGE_ENDPOINT', bucket: 'TIGRIS_STORAGE_BUCKET', accessKeyId: 'TIGRIS_STORAGE_ACCESS_KEY_ID', From 0f9152ac7645abe0d26015df41cebe11f3f5e031 Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Mon, 13 Apr 2026 17:14:55 +0200 Subject: [PATCH 3/4] chore(shared): remove generateSignatureHeaders from global export --- shared/http-client.ts | 2 +- shared/index.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/http-client.ts b/shared/http-client.ts index 81c8e73..34ec28f 100644 --- a/shared/http-client.ts +++ b/shared/http-client.ts @@ -48,7 +48,7 @@ const cachedHttpClients = new Map(); /** * Generate AWS Signature V4 headers for a request */ -export async function generateSignatureHeaders( +async function generateSignatureHeaders( method: string, url: URL, headers: Record, diff --git a/shared/index.ts b/shared/index.ts index 9123a2a..4c2eedd 100644 --- a/shared/index.ts +++ b/shared/index.ts @@ -4,7 +4,6 @@ export { executeWithConcurrency, handleError, toError } from './utils'; export { TigrisHeaders } from './headers'; export { createTigrisHttpClient, - generateSignatureHeaders, type HttpClientRequest, type HttpClientResponse, type TigrisHttpClient, From 901079297c24cad3837236a39ee8859a09a5528a Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Mon, 13 Apr 2026 19:56:18 +0200 Subject: [PATCH 4/4] feat(storage): object migration methods --- packages/storage/src/lib/object/get.ts | 26 +----- packages/storage/src/lib/object/list.ts | 17 ++++ packages/storage/src/lib/object/migrate.ts | 94 ++++++++++++++++++++++ packages/storage/src/lib/object/remove.ts | 14 +--- packages/storage/src/lib/object/update.ts | 2 +- packages/storage/src/lib/utils.ts | 23 ------ packages/storage/src/server.ts | 13 +-- shared/headers.ts | 5 ++ 8 files changed, 127 insertions(+), 67 deletions(-) create mode 100644 packages/storage/src/lib/object/migrate.ts diff --git a/packages/storage/src/lib/object/get.ts b/packages/storage/src/lib/object/get.ts index cce3752..2be058f 100644 --- a/packages/storage/src/lib/object/get.ts +++ b/packages/storage/src/lib/object/get.ts @@ -1,6 +1,6 @@ import { GetObjectCommand } from '@aws-sdk/client-s3'; import type { HttpRequest } from '@aws-sdk/types'; -import { TigrisHeaders } from '@shared/index'; +import { handleError, TigrisHeaders } from '@shared/index'; import { config } from '../config'; import { createTigrisClient } from '../tigris-client'; import type { TigrisStorageConfig, TigrisStorageResponse } from '../types'; @@ -104,27 +104,3 @@ export async function get( return handleError(error as Error); } } - -const handleError = (error: Error) => { - let errorMessage: string | undefined; - - if ((error as { Code?: string }).Code === 'AccessDenied') { - errorMessage = - 'Access denied while downloading from Tigris Storage. Please check your credentials.'; - } - if ((error as { Code?: string }).Code === 'NoSuchKey') { - errorMessage = 'File not found in Tigris Storage'; - } - - if (errorMessage) { - return { - error: new Error(errorMessage), - }; - } - - return { - error: new Error( - error?.message || 'Unexpected error while downloading from Tigris Storage' - ), - }; -}; diff --git a/packages/storage/src/lib/object/list.ts b/packages/storage/src/lib/object/list.ts index b9f0631..2b933da 100644 --- a/packages/storage/src/lib/object/list.ts +++ b/packages/storage/src/lib/object/list.ts @@ -11,6 +11,7 @@ export type ListOptions = { limit?: number; paginationToken?: string; snapshotVersion?: string; + source?: 'tigris' | 'shadow'; config?: TigrisStorageConfig; }; @@ -23,6 +24,7 @@ export type ListItem = { export type ListResponse = { items: ListItem[]; + commonPrefixes: string[]; paginationToken: string | undefined; hasMore: boolean; }; @@ -65,6 +67,18 @@ export async function list( ); } + if (options?.source) { + const source = options.source; + list.middlewareStack.add( + (next) => async (args) => { + const req = args.request as HttpRequest; + req.headers[TigrisHeaders.BUCKET_LIST_SOURCE] = source; + return next(args); + }, + { name: 'X-Tigris-List-Source-Middleware', step: 'build', override: true } + ); + } + try { return tigrisClient .send(list) @@ -78,6 +92,9 @@ export async function list( size: item.Size ?? 0, lastModified: item.LastModified ?? new Date(), })) ?? [], + commonPrefixes: + res.CommonPrefixes?.map((p) => p.Prefix ?? '').filter(Boolean) ?? + [], paginationToken: res.NextContinuationToken, hasMore: res.IsTruncated ?? false, }, diff --git a/packages/storage/src/lib/object/migrate.ts b/packages/storage/src/lib/object/migrate.ts new file mode 100644 index 0000000..f514c08 --- /dev/null +++ b/packages/storage/src/lib/object/migrate.ts @@ -0,0 +1,94 @@ +import { HeadObjectCommand } from '@aws-sdk/client-s3'; +import type { HttpRequest, HttpResponse } from '@aws-sdk/types'; +import { handleError, TigrisHeaders } from '@shared/index'; +import { config } from '../config'; +import { createTigrisClient } from '../tigris-client'; +import type { TigrisStorageConfig, TigrisStorageResponse } from '../types'; + +export type MigrateOptions = { + config?: TigrisStorageConfig; +}; + +export async function migrate( + path: string, + options?: MigrateOptions +): Promise> { + const { data: tigrisClient, error } = createTigrisClient(options?.config); + + if (error) { + return { error }; + } + + const head = new HeadObjectCommand({ + Bucket: options?.config?.bucket ?? config.bucket, + Key: path, + }); + + head.middlewareStack.add( + (next) => async (args) => { + const req = args.request as HttpRequest; + req.headers[TigrisHeaders.SCHEDULE_MIGRATION] = 'true'; + const result = await next(args); + return result; + }, + { + step: 'build', + } + ); + + try { + return tigrisClient + .send(head) + .then(async () => { + return { + data: undefined, + }; + }) + .catch(handleError); + } catch (error) { + return handleError(error as Error); + } +} + +export async function isMigrated( + path: string, + options?: MigrateOptions +): Promise> { + const { data: tigrisClient, error } = createTigrisClient(options?.config); + + if (error) { + return { error }; + } + + const head = new HeadObjectCommand({ + Bucket: options?.config?.bucket ?? config.bucket, + Key: path, + }); + + let responseHeaders: Record = {}; + + head.middlewareStack.add( + (next) => async (args) => { + const result = await next(args); + responseHeaders = (result.response as HttpResponse).headers; + + return result; + }, + { + step: 'deserialize', + } + ); + + try { + return tigrisClient + .send(head) + .then(async () => { + return { + data: TigrisHeaders.SERVED_FROM.toLowerCase() in responseHeaders, + }; + }) + .catch(handleError); + } catch (error) { + return handleError(error as Error); + } +} diff --git a/packages/storage/src/lib/object/remove.ts b/packages/storage/src/lib/object/remove.ts index 2403d1a..8c168b2 100644 --- a/packages/storage/src/lib/object/remove.ts +++ b/packages/storage/src/lib/object/remove.ts @@ -2,6 +2,7 @@ import { DeleteObjectCommand } from '@aws-sdk/client-s3'; import { config } from '../config'; import { createTigrisClient } from '../tigris-client'; import type { TigrisStorageConfig, TigrisStorageResponse } from '../types'; +import { handleError } from '@shared/utils'; export type RemoveOptions = { config?: TigrisStorageConfig; @@ -29,17 +30,6 @@ export async function remove( }) .catch(handleError); } catch (error) { - return handleError(error); + return handleError(error as Error); } } - -const handleError = (error: unknown) => { - if ((error as { Code?: string }).Code === 'AccessDenied') { - return { - error: new Error('Access denied while deleting file'), - }; - } - return { - error: new Error('Error deleting file'), - }; -}; diff --git a/packages/storage/src/lib/object/update.ts b/packages/storage/src/lib/object/update.ts index 6c1130b..ef02c93 100644 --- a/packages/storage/src/lib/object/update.ts +++ b/packages/storage/src/lib/object/update.ts @@ -3,8 +3,8 @@ import { createTigrisClient } from '../tigris-client'; import { TigrisHeaders } from '@shared/headers'; import type { TigrisStorageConfig, TigrisStorageResponse } from '../types'; import { config, missingConfigError } from '../config'; -import { handleError } from '../utils'; import { createStorageClient } from '../http-client'; +import { handleError } from '@shared/utils'; export type UpdateObjectOptions = { config?: TigrisStorageConfig; diff --git a/packages/storage/src/lib/utils.ts b/packages/storage/src/lib/utils.ts index 2573aac..74786b7 100644 --- a/packages/storage/src/lib/utils.ts +++ b/packages/storage/src/lib/utils.ts @@ -4,26 +4,3 @@ export const addRandomSuffix = (path: string) => { const baseName = pathParts.join('.'); return `${baseName}-${new Date().getTime().toString(36) + Math.random().toString(36).substring(2, 8)}${extension ? `.${extension}` : ''}`; }; - -export const handleError = (error: Error) => { - let errorMessage: string | undefined; - - if ((error as { Code?: string }).Code === 'AccessDenied') { - errorMessage = 'Access denied. Please check your credentials.'; - } - if ((error as { Code?: string }).Code === 'NoSuchKey') { - errorMessage = 'File not found in Tigris Storage'; - } - - if (errorMessage) { - return { - error: new Error(errorMessage), - }; - } - - return { - error: new Error( - error?.message || 'Unexpected error while processing request' - ), - }; -}; diff --git a/packages/storage/src/server.ts b/packages/storage/src/server.ts index 4e637e4..5068a9e 100644 --- a/packages/storage/src/server.ts +++ b/packages/storage/src/server.ts @@ -36,9 +36,9 @@ export { setBucketTtl, type SetBucketTtlOptions } from './lib/bucket/set/ttl'; export { createBucketSnapshot, listBucketSnapshots, + type BucketSnapshot, type CreateBucketSnapshotOptions, type CreateBucketSnapshotResponse, - type BucketSnapshot, type ListBucketSnapshotsOptions, type ListBucketSnapshotsResponse, } from './lib/bucket/snapshot'; @@ -53,6 +53,11 @@ export { type UpdateBucketResponse, } from './lib/bucket/types'; export { updateBucket, type UpdateBucketOptions } from './lib/bucket/update'; +export { + bundle, + type BundleOptions, + type BundleResponse, +} from './lib/object/bundle'; export { get, type GetOptions, type GetResponse } from './lib/object/get'; export { head, type HeadOptions, type HeadResponse } from './lib/object/head'; export { @@ -61,6 +66,7 @@ export { type ListOptions, type ListResponse, } from './lib/object/list'; +export { isMigrated, migrate, type MigrateOptions } from './lib/object/migrate'; export { completeMultipartUpload, getPartsPresignedUrls, @@ -93,9 +99,4 @@ export { handleClientUpload, type ClientUploadRequest, } from './lib/upload/server'; -export { - bundle, - type BundleOptions, - type BundleResponse, -} from './lib/object/bundle'; export { UploadAction } from './lib/upload/shared'; diff --git a/shared/headers.ts b/shared/headers.ts index e74e3eb..a9035e2 100644 --- a/shared/headers.ts +++ b/shared/headers.ts @@ -6,6 +6,10 @@ export enum TigrisHeaders { NAMESPACE = 'X-Tigris-Namespace', STORAGE_CLASS = 'X-Amz-Storage-Class', + BUCKET_LIST_SOURCE = 'X-Tigris-List-Source', // tigris or shadow + SCHEDULE_MIGRATION = 'X-Tigris-Schedule-Migration', + SERVED_FROM = 'X-Tigris-Served-From', + REGIONS = 'X-Tigris-Regions', SNAPSHOT = 'X-Tigris-Snapshot', SNAPSHOT_VERSION = 'X-Tigris-Snapshot-Version', @@ -13,6 +17,7 @@ export enum TigrisHeaders { HAS_FORKS = 'X-Tigris-Is-Fork-Parent', FORK_SOURCE_BUCKET = 'X-Tigris-Fork-Source-Bucket', FORK_SOURCE_BUCKET_SNAPSHOT = 'X-Tigris-Fork-Source-Bucket-Snapshot', + RENAME = 'X-Tigris-Rename', COPY_SOURCE = 'X-Amz-Copy-Source', BUNDLE_FORMAT = 'X-Tigris-Bundle-Format',