storybooker 0.19.3 → 0.22.0-canary.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 +40 -18
- package/dist/adapters/_internal/queue.d.mts +127 -0
- package/dist/aws-dynamodb.d.mts +22 -0
- package/dist/aws-dynamodb.mjs +118 -0
- package/dist/aws-dynamodb.mjs.map +1 -0
- package/dist/aws-s3.d.mts +20 -0
- package/dist/aws-s3.mjs +96 -0
- package/dist/aws-s3.mjs.map +1 -0
- package/dist/azure-blob-storage.d.mts +20 -0
- package/dist/azure-blob-storage.mjs +126 -0
- package/dist/azure-blob-storage.mjs.map +1 -0
- package/dist/azure-cosmos-db.d.mts +23 -0
- package/dist/azure-cosmos-db.mjs +87 -0
- package/dist/azure-cosmos-db.mjs.map +1 -0
- package/dist/azure-data-tables.d.mts +23 -0
- package/dist/azure-data-tables.mjs +127 -0
- package/dist/azure-data-tables.mjs.map +1 -0
- package/dist/azure-easy-auth.d.mts +50 -0
- package/dist/azure-easy-auth.mjs +88 -0
- package/dist/azure-easy-auth.mjs.map +1 -0
- package/dist/azure-functions.d.mts +62 -0
- package/dist/azure-functions.mjs +147 -0
- package/dist/azure-functions.mjs.map +1 -0
- package/dist/fs.d.mts +37 -0
- package/dist/fs.mjs +240 -0
- package/dist/fs.mjs.map +1 -0
- package/dist/gcp-big-table.d.mts +23 -0
- package/dist/gcp-big-table.mjs +92 -0
- package/dist/gcp-big-table.mjs.map +1 -0
- package/dist/gcp-firestore.d.mts +22 -0
- package/dist/gcp-firestore.mjs +87 -0
- package/dist/gcp-firestore.mjs.map +1 -0
- package/dist/gcp-storage.d.mts +20 -0
- package/dist/gcp-storage.mjs +96 -0
- package/dist/gcp-storage.mjs.map +1 -0
- package/dist/handlers/handle-process-zip.mjs +90 -0
- package/dist/handlers/handle-process-zip.mjs.map +1 -0
- package/dist/handlers/handle-purge.d.mts +12 -0
- package/dist/handlers/handle-purge.mjs +36 -0
- package/dist/handlers/handle-purge.mjs.map +1 -0
- package/dist/handlers/handle-serve-storybook.mjs +94 -0
- package/dist/handlers/handle-serve-storybook.mjs.map +1 -0
- package/dist/index.d.mts +28 -0
- package/dist/index.mjs +62 -0
- package/dist/index.mjs.map +1 -0
- package/dist/models/builds-model.mjs +248 -0
- package/dist/models/builds-model.mjs.map +1 -0
- package/dist/models/builds-schema.d.mts +171 -0
- package/dist/models/builds-schema.mjs +67 -0
- package/dist/models/builds-schema.mjs.map +1 -0
- package/dist/models/projects-model.mjs +122 -0
- package/dist/models/projects-model.mjs.map +1 -0
- package/dist/models/projects-schema.d.mts +70 -0
- package/dist/models/projects-schema.mjs +37 -0
- package/dist/models/projects-schema.mjs.map +1 -0
- package/dist/models/tags-model.mjs +110 -0
- package/dist/models/tags-model.mjs.map +1 -0
- package/dist/models/tags-schema.d.mts +76 -0
- package/dist/models/tags-schema.mjs +34 -0
- package/dist/models/tags-schema.mjs.map +1 -0
- package/dist/models/~model.mjs +43 -0
- package/dist/models/~model.mjs.map +1 -0
- package/dist/models/~shared-schema.d.mts +1 -0
- package/dist/models/~shared-schema.mjs +20 -0
- package/dist/models/~shared-schema.mjs.map +1 -0
- package/dist/mysql.d.mts +39 -0
- package/dist/mysql.mjs +151 -0
- package/dist/mysql.mjs.map +1 -0
- package/dist/redis.d.mts +33 -0
- package/dist/redis.mjs +118 -0
- package/dist/redis.mjs.map +1 -0
- package/dist/routers/account-router.mjs +91 -0
- package/dist/routers/account-router.mjs.map +1 -0
- package/dist/routers/builds-router.mjs +347 -0
- package/dist/routers/builds-router.mjs.map +1 -0
- package/dist/routers/projects-router.mjs +236 -0
- package/dist/routers/projects-router.mjs.map +1 -0
- package/dist/routers/root-router.mjs +108 -0
- package/dist/routers/root-router.mjs.map +1 -0
- package/dist/routers/tags-router.mjs +269 -0
- package/dist/routers/tags-router.mjs.map +1 -0
- package/dist/routers/tasks-router.mjs +71 -0
- package/dist/routers/tasks-router.mjs.map +1 -0
- package/dist/urls.d.mts +47 -0
- package/dist/urls.mjs +208 -0
- package/dist/urls.mjs.map +1 -0
- package/dist/utils/adapter-utils.d.mts +14 -0
- package/dist/utils/adapter-utils.mjs +14 -0
- package/dist/utils/adapter-utils.mjs.map +1 -0
- package/dist/utils/auth.mjs +25 -0
- package/dist/utils/auth.mjs.map +1 -0
- package/dist/utils/error.d.mts +21 -0
- package/dist/utils/error.mjs +109 -0
- package/dist/utils/error.mjs.map +1 -0
- package/dist/utils/file-utils.mjs +16 -0
- package/dist/utils/file-utils.mjs.map +1 -0
- package/dist/utils/openapi-utils.mjs +45 -0
- package/dist/utils/openapi-utils.mjs.map +1 -0
- package/dist/utils/request.mjs +35 -0
- package/dist/utils/request.mjs.map +1 -0
- package/dist/utils/response.mjs +24 -0
- package/dist/utils/response.mjs.map +1 -0
- package/dist/utils/store.mjs +54 -0
- package/dist/utils/store.mjs.map +1 -0
- package/dist/utils/ui-utils.mjs +38 -0
- package/dist/utils/ui-utils.mjs.map +1 -0
- package/dist/utils/url-utils.d.mts +10 -0
- package/dist/utils/url-utils.mjs +54 -0
- package/dist/utils/url-utils.mjs.map +1 -0
- package/dist/~internal/adapter/auth.d.mts +123 -0
- package/dist/~internal/adapter/auth.mjs +20 -0
- package/dist/~internal/adapter/auth.mjs.map +1 -0
- package/dist/~internal/adapter/database.d.mts +240 -0
- package/dist/~internal/adapter/database.mjs +63 -0
- package/dist/~internal/adapter/database.mjs.map +1 -0
- package/dist/~internal/adapter/logger.d.mts +34 -0
- package/dist/~internal/adapter/logger.mjs +13 -0
- package/dist/~internal/adapter/logger.mjs.map +1 -0
- package/dist/~internal/adapter/storage.d.mts +208 -0
- package/dist/~internal/adapter/storage.mjs +63 -0
- package/dist/~internal/adapter/storage.mjs.map +1 -0
- package/dist/~internal/adapter/ui.d.mts +109 -0
- package/dist/~internal/adapter/ui.mjs +1 -0
- package/dist/~internal/adapter.d.mts +8 -0
- package/dist/~internal/adapter.mjs +6 -0
- package/dist/~internal/constants.d.mts +24 -0
- package/dist/~internal/constants.mjs +32 -0
- package/dist/~internal/constants.mjs.map +1 -0
- package/dist/~internal/mimes.d.mts +449 -0
- package/dist/~internal/mimes.mjs +454 -0
- package/dist/~internal/mimes.mjs.map +1 -0
- package/dist/~internal/router.d.mts +1651 -0
- package/dist/~internal/router.mjs +39 -0
- package/dist/~internal/router.mjs.map +1 -0
- package/dist/~internal/types.d.mts +77 -0
- package/dist/~internal/types.mjs +1 -0
- package/dist/~internal/utils.d.mts +4 -0
- package/dist/~internal/utils.mjs +5 -0
- package/openapi.json +3162 -0
- package/package.json +148 -27
- package/src/adapters/_internal/auth.ts +135 -0
- package/src/adapters/_internal/database.ts +241 -0
- package/src/adapters/_internal/index.ts +8 -0
- package/src/adapters/_internal/logger.ts +41 -0
- package/src/adapters/_internal/queue.ts +151 -0
- package/src/adapters/_internal/storage.ts +197 -0
- package/src/adapters/_internal/ui.ts +103 -0
- package/src/adapters/aws-dynamodb.ts +201 -0
- package/src/adapters/aws-s3.ts +160 -0
- package/src/adapters/azure-blob-storage.ts +223 -0
- package/src/adapters/azure-cosmos-db.ts +158 -0
- package/src/adapters/azure-data-tables.ts +223 -0
- package/src/adapters/azure-easy-auth.ts +174 -0
- package/src/adapters/azure-functions.ts +242 -0
- package/src/adapters/fs.ts +398 -0
- package/src/adapters/gcp-big-table.ts +157 -0
- package/src/adapters/gcp-firestore.ts +146 -0
- package/src/adapters/gcp-storage.ts +141 -0
- package/src/adapters/mysql.ts +296 -0
- package/src/adapters/redis.ts +242 -0
- package/src/handlers/handle-process-zip.ts +117 -0
- package/src/handlers/handle-purge.ts +65 -0
- package/src/handlers/handle-serve-storybook.ts +101 -0
- package/src/index.ts +81 -16
- package/src/mocks/mock-auth-service.ts +51 -0
- package/src/mocks/mock-store.ts +26 -0
- package/src/models/builds-model.ts +373 -0
- package/src/models/builds-schema.ts +84 -0
- package/src/models/projects-model.ts +177 -0
- package/src/models/projects-schema.ts +69 -0
- package/src/models/tags-model.ts +138 -0
- package/src/models/tags-schema.ts +45 -0
- package/src/models/~model.ts +79 -0
- package/src/models/~shared-schema.ts +14 -0
- package/src/routers/_app-router.ts +57 -0
- package/src/routers/account-router.ts +136 -0
- package/src/routers/builds-router.ts +464 -0
- package/src/routers/projects-router.ts +309 -0
- package/src/routers/root-router.ts +127 -0
- package/src/routers/tags-router.ts +339 -0
- package/src/routers/tasks-router.ts +75 -0
- package/src/types.ts +107 -0
- package/src/urls.ts +327 -0
- package/src/utils/adapter-utils.ts +26 -0
- package/src/utils/auth.test.ts +71 -0
- package/src/utils/auth.ts +39 -0
- package/src/utils/constants.ts +31 -0
- package/src/utils/date-utils.ts +10 -0
- package/src/utils/error.test.ts +86 -0
- package/src/utils/error.ts +140 -0
- package/src/utils/file-utils.test.ts +65 -0
- package/src/utils/file-utils.ts +43 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/mime-utils.ts +457 -0
- package/src/utils/openapi-utils.ts +49 -0
- package/src/utils/request.ts +97 -0
- package/src/utils/response.ts +20 -0
- package/src/utils/store.ts +85 -0
- package/src/utils/story-utils.ts +42 -0
- package/src/utils/text-utils.ts +10 -0
- package/src/utils/ui-utils.ts +57 -0
- package/src/utils/url-utils.ts +113 -0
- package/dist/index.js +0 -554
- package/src/commands/create.ts +0 -263
- package/src/commands/purge.ts +0 -70
- package/src/commands/test.ts +0 -42
- package/src/service-schema.d.ts +0 -2023
- package/src/utils/auth-utils.ts +0 -31
- package/src/utils/pkg-utils.ts +0 -37
- package/src/utils/sb-build.ts +0 -55
- package/src/utils/sb-test.ts +0 -115
- package/src/utils/schema-utils.ts +0 -123
- package/src/utils/stream-utils.ts +0 -72
- package/src/utils/types.ts +0 -4
- package/src/utils/zip.ts +0 -77
package/README.md
CHANGED
|
@@ -1,27 +1,49 @@
|
|
|
1
|
-
# StoryBooker
|
|
1
|
+
# StoryBooker Core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The core contains the routing logic and UI for StoryBooker.
|
|
4
|
+
The core can be extended to be used with any platform that supports standard fetch (Request+Response) or Hono server.
|
|
4
5
|
|
|
5
6
|
Docs: https://storybooker.js.org/docs/
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Running on basic Node server
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
> Refer Hono docs: https://hono.dev/docs/getting-started/nodejs
|
|
10
11
|
|
|
11
|
-
```
|
|
12
|
-
|
|
12
|
+
```js
|
|
13
|
+
import { serve } from "@hono/node-server";
|
|
14
|
+
import { createHonoRouter } from "storybooker";
|
|
15
|
+
import {
|
|
16
|
+
createLocalFileDatabaseAdapter,
|
|
17
|
+
createLocalFileStorageAdapter,
|
|
18
|
+
} from "storybooker/fs";
|
|
19
|
+
import { createBasicUIAdapter } from "@storybooker/ui";
|
|
20
|
+
|
|
21
|
+
const app = createHonoRouter({
|
|
22
|
+
database: createLocalFileDatabaseAdapter(),
|
|
23
|
+
storage: createLocalFileStorageAdapter(),
|
|
24
|
+
ui: createBasicUIAdapter(), // remove to create headless service
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
serve(app);
|
|
13
28
|
```
|
|
14
29
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
## Running on basic Deno server
|
|
31
|
+
|
|
32
|
+
> Refer Hono docs: https://hono.dev/docs/getting-started/deno
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
import { createHonoRouter } from "jsr:@storybooker/core";
|
|
36
|
+
import {
|
|
37
|
+
createLocalFileDatabaseAdapter,
|
|
38
|
+
createLocalFileStorageAdapter,
|
|
39
|
+
} from "jsr:@storybooker/core/fs";
|
|
40
|
+
import { createBasicUIAdapter } from "npm:@storybooker/ui";
|
|
41
|
+
|
|
42
|
+
const app = createHonoRouter({
|
|
43
|
+
database: createLocalFileDatabaseAdapter(),
|
|
44
|
+
storage: createLocalFileStorageAdapter(),
|
|
45
|
+
ui: createBasicUIAdapter(), // remove to create headless service
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export default router;
|
|
27
49
|
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { StoryBookerAdapterMetadata } from "../../utils/adapter-utils.mjs";
|
|
2
|
+
import { LoggerAdapter } from "../../~internal/adapter/logger.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/_internal/queue.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Service adapter to interact with queueing service.
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* The adapter should provide callbacks to perform operations
|
|
11
|
+
* to an existing queue like send and receive messages.
|
|
12
|
+
*
|
|
13
|
+
* - `topic`: A topic/queue to hold messages.
|
|
14
|
+
* - `message`: A single message in topic which contains data and metadata.
|
|
15
|
+
* Each message has a unique identifier.
|
|
16
|
+
*/
|
|
17
|
+
interface QueueAdapter {
|
|
18
|
+
/**
|
|
19
|
+
* Metadata about the adapter.
|
|
20
|
+
*/
|
|
21
|
+
metadata: StoryBookerAdapterMetadata;
|
|
22
|
+
/**
|
|
23
|
+
* An optional method that is called on app boot-up
|
|
24
|
+
* to run async setup functions.
|
|
25
|
+
* @param options Common options like abortSignal.
|
|
26
|
+
* @throws if an error occur during initialisation.
|
|
27
|
+
*/
|
|
28
|
+
init?: (options: QueueAdapterOptions) => Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* List all topics available in the queue service.
|
|
31
|
+
* @param options Common options like abortSignal.
|
|
32
|
+
* @returns A list of names/IDs of the topics.
|
|
33
|
+
* @throws If the queue service is not connected.
|
|
34
|
+
*/
|
|
35
|
+
listTopics: (options: QueueAdapterOptions) => Promise<string[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Create a topic used for different message types.
|
|
38
|
+
* @param topicId ID of the topic
|
|
39
|
+
* @param options Common options like abortSignal.
|
|
40
|
+
* @throws if topic with ID already exists.
|
|
41
|
+
*/
|
|
42
|
+
createTopic: (topicId: string, options: QueueAdapterOptions) => Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Delete an existing topic.
|
|
45
|
+
* @param topicId ID of the topic
|
|
46
|
+
* @param options Common options like abortSignal.
|
|
47
|
+
* @throws if topic with ID does not exist.
|
|
48
|
+
*/
|
|
49
|
+
deleteTopic: (topicId: string, options: QueueAdapterOptions) => Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Check if topic exists.
|
|
52
|
+
* @param topicId ID of the topic
|
|
53
|
+
* @param options Common options like abortSignal.
|
|
54
|
+
* @returns if topic is available or not
|
|
55
|
+
* @throws never.
|
|
56
|
+
*/
|
|
57
|
+
hasTopic: (topicId: string, options: QueueAdapterOptions) => Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* Send a message to the specified topic.
|
|
60
|
+
* @param topicId ID of the topic
|
|
61
|
+
* @param message Message data to be sent
|
|
62
|
+
* @param options Common options like abortSignal.
|
|
63
|
+
* @returns Message ID if successful
|
|
64
|
+
* @throws if the topic does not exist.
|
|
65
|
+
*/
|
|
66
|
+
sendMessage: <Message extends StoryBookerQueueMessage>(topicId: string, message: Omit<Message, "id" | "timestamp">, options: QueueAdapterOptions) => Promise<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Receive messages from the specified topic.
|
|
69
|
+
* @param topicId ID of the topic
|
|
70
|
+
* @param receiveOptions Options to configure message receiving
|
|
71
|
+
* @param options Common options like abortSignal.
|
|
72
|
+
* @returns List of messages
|
|
73
|
+
* @throws if the topic does not exist.
|
|
74
|
+
*/
|
|
75
|
+
receiveMessages: <Message extends StoryBookerQueueMessage>(topicId: string, receiveOptions: QueueMessageReceiveOptions, options: QueueAdapterOptions) => Promise<Message[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Acknowledge that a message has been processed.
|
|
78
|
+
* @param topicId ID of the topic
|
|
79
|
+
* @param messageId ID of the message
|
|
80
|
+
* @param options Common options like abortSignal.
|
|
81
|
+
* @throws if the topic or message does not exist.
|
|
82
|
+
*/
|
|
83
|
+
acknowledgeMessage: (topicId: string, messageId: string, options: QueueAdapterOptions) => Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Get message count in a topic.
|
|
86
|
+
* @param topicId ID of the topic
|
|
87
|
+
* @param options Common options like abortSignal.
|
|
88
|
+
* @returns Number of messages in the topic
|
|
89
|
+
* @throws if the topic does not exist.
|
|
90
|
+
*/
|
|
91
|
+
getMessageCount: (topicId: string, options: QueueAdapterOptions) => Promise<number>;
|
|
92
|
+
/**
|
|
93
|
+
* Purge all messages from a topic.
|
|
94
|
+
* @param topicId ID of the topic
|
|
95
|
+
* @param options Common options like abortSignal.
|
|
96
|
+
* @throws if the topic does not exist.
|
|
97
|
+
*/
|
|
98
|
+
purgeMessages: (topicId: string, options: QueueAdapterOptions) => Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Base Message shape used in StoryBooker Queue.
|
|
102
|
+
* Should always contain fields 'id' and 'timestamp'.
|
|
103
|
+
*/
|
|
104
|
+
interface StoryBookerQueueMessage {
|
|
105
|
+
id: string;
|
|
106
|
+
timestamp: number;
|
|
107
|
+
data: Record<string, unknown>;
|
|
108
|
+
attributes?: Record<string, string>;
|
|
109
|
+
}
|
|
110
|
+
/** Common Queue adapter options. */
|
|
111
|
+
interface QueueAdapterOptions {
|
|
112
|
+
/** A signal that can be used to cancel the request handling. */
|
|
113
|
+
abortSignal?: AbortSignal;
|
|
114
|
+
/** Logger */
|
|
115
|
+
logger: LoggerAdapter;
|
|
116
|
+
}
|
|
117
|
+
interface QueueMessageReceiveOptions {
|
|
118
|
+
/** Maximum number of messages to receive */
|
|
119
|
+
maxMessages?: number;
|
|
120
|
+
/** Visibility timeout in seconds */
|
|
121
|
+
visibilityTimeout?: number;
|
|
122
|
+
/** Wait time for long polling in seconds */
|
|
123
|
+
waitTimeSeconds?: number;
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { QueueAdapter, QueueAdapterOptions, QueueMessageReceiveOptions, StoryBookerQueueMessage };
|
|
127
|
+
//# sourceMappingURL=queue.d.mts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DatabaseAdapter } from "./~internal/adapter/database.mjs";
|
|
2
|
+
import * as Dynamo from "@aws-sdk/client-dynamodb";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/aws-dynamodb.d.ts
|
|
5
|
+
declare class AwsDynamoDatabaseService implements DatabaseAdapter {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(client: Dynamo.DynamoDBClient);
|
|
8
|
+
metadata: DatabaseAdapter["metadata"];
|
|
9
|
+
listCollections: DatabaseAdapter["listCollections"];
|
|
10
|
+
createCollection: DatabaseAdapter["createCollection"];
|
|
11
|
+
hasCollection: DatabaseAdapter["hasCollection"];
|
|
12
|
+
deleteCollection: DatabaseAdapter["deleteCollection"];
|
|
13
|
+
listDocuments: DatabaseAdapter["listDocuments"];
|
|
14
|
+
getDocument: DatabaseAdapter["getDocument"];
|
|
15
|
+
createDocument: DatabaseAdapter["createDocument"];
|
|
16
|
+
hasDocument: DatabaseAdapter["hasDocument"];
|
|
17
|
+
deleteDocument: DatabaseAdapter["deleteDocument"];
|
|
18
|
+
updateDocument: DatabaseAdapter["updateDocument"];
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { AwsDynamoDatabaseService };
|
|
22
|
+
//# sourceMappingURL=aws-dynamodb.d.mts.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { DatabaseAdapterErrors } from "./~internal/adapter/database.mjs";
|
|
2
|
+
import * as Dynamo from "@aws-sdk/client-dynamodb";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/aws-dynamodb.ts
|
|
5
|
+
var AwsDynamoDatabaseService = class {
|
|
6
|
+
#client;
|
|
7
|
+
constructor(client) {
|
|
8
|
+
this.#client = client;
|
|
9
|
+
}
|
|
10
|
+
metadata = { name: "AWS DynamoDB" };
|
|
11
|
+
listCollections = async (options) => {
|
|
12
|
+
return (await this.#client.send(new Dynamo.ListTablesCommand({}), { abortSignal: options.abortSignal })).TableNames ?? [];
|
|
13
|
+
};
|
|
14
|
+
createCollection = async (collectionId, options) => {
|
|
15
|
+
try {
|
|
16
|
+
await this.#client.send(new Dynamo.CreateTableCommand({
|
|
17
|
+
AttributeDefinitions: [{
|
|
18
|
+
AttributeName: "id",
|
|
19
|
+
AttributeType: "S"
|
|
20
|
+
}],
|
|
21
|
+
BillingMode: "PAY_PER_REQUEST",
|
|
22
|
+
KeySchema: [{
|
|
23
|
+
AttributeName: "id",
|
|
24
|
+
KeyType: "HASH"
|
|
25
|
+
}],
|
|
26
|
+
TableName: collectionId
|
|
27
|
+
}), { abortSignal: options.abortSignal });
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId, error);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
hasCollection = async (collectionId, options) => {
|
|
33
|
+
try {
|
|
34
|
+
const response = await this.#client.send(new Dynamo.DescribeTableCommand({ TableName: collectionId }), { abortSignal: options.abortSignal });
|
|
35
|
+
return Boolean(response.Table);
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
deleteCollection = async (collectionId, options) => {
|
|
41
|
+
try {
|
|
42
|
+
await this.#client.send(new Dynamo.DeleteTableCommand({ TableName: collectionId }), { abortSignal: options.abortSignal });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
listDocuments = async (collectionId, _listOptions, options) => {
|
|
48
|
+
return ((await this.#client.send(new Dynamo.ScanCommand({ TableName: collectionId }), { abortSignal: options.abortSignal })).Items ?? []).map((item) => {
|
|
49
|
+
const doc = {};
|
|
50
|
+
for (const [key, value] of Object.entries(item)) doc[key] = value.S ?? value.N ?? value.BOOL ?? value.NULL ?? value;
|
|
51
|
+
return doc;
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
getDocument = async (collectionId, documentId, options) => {
|
|
55
|
+
try {
|
|
56
|
+
const response = await this.#client.send(new Dynamo.GetItemCommand({
|
|
57
|
+
Key: { id: { S: documentId } },
|
|
58
|
+
TableName: collectionId
|
|
59
|
+
}), { abortSignal: options.abortSignal });
|
|
60
|
+
const document = response.Item ? Object.fromEntries(Object.entries(response.Item).map(([key, value]) => [key, value.S ?? value.N ?? value.BOOL ?? value.NULL ?? value])) : void 0;
|
|
61
|
+
if (!document) throw new Error("Document not found");
|
|
62
|
+
document["id"] = documentId;
|
|
63
|
+
return document;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
createDocument = async (collectionId, documentData, options) => {
|
|
69
|
+
try {
|
|
70
|
+
await this.#client.send(new Dynamo.PutItemCommand({
|
|
71
|
+
Item: Object.fromEntries(Object.entries(documentData).map(([key, value]) => [key, { S: String(value) }])),
|
|
72
|
+
TableName: collectionId
|
|
73
|
+
}), { abortSignal: options.abortSignal });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw new DatabaseAdapterErrors.DocumentAlreadyExistsError(collectionId, documentData.id, error);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
hasDocument = async (collectionId, documentId, options) => {
|
|
79
|
+
const response = await this.#client.send(new Dynamo.GetItemCommand({
|
|
80
|
+
Key: { id: { S: documentId } },
|
|
81
|
+
TableName: collectionId
|
|
82
|
+
}), { abortSignal: options.abortSignal });
|
|
83
|
+
return Boolean(response.Item);
|
|
84
|
+
};
|
|
85
|
+
deleteDocument = async (collectionId, documentId, options) => {
|
|
86
|
+
try {
|
|
87
|
+
await this.#client.send(new Dynamo.DeleteItemCommand({
|
|
88
|
+
Key: { id: { S: documentId } },
|
|
89
|
+
TableName: collectionId
|
|
90
|
+
}), { abortSignal: options.abortSignal });
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
updateDocument = async (collectionId, documentId, documentData, options) => {
|
|
96
|
+
const updateExpr = [];
|
|
97
|
+
const exprAttrValues = {};
|
|
98
|
+
for (const [key, value] of Object.entries(documentData)) {
|
|
99
|
+
updateExpr.push(`#${key} = :${key}`);
|
|
100
|
+
exprAttrValues[`:${key}`] = { S: String(value) };
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
await this.#client.send(new Dynamo.UpdateItemCommand({
|
|
104
|
+
ExpressionAttributeNames: Object.fromEntries(Object.keys(documentData).map((k) => [`#${k}`, k])),
|
|
105
|
+
ExpressionAttributeValues: exprAttrValues,
|
|
106
|
+
Key: { id: { S: documentId } },
|
|
107
|
+
TableName: collectionId,
|
|
108
|
+
UpdateExpression: `SET ${updateExpr.join(", ")}`
|
|
109
|
+
}), { abortSignal: options.abortSignal });
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
//#endregion
|
|
117
|
+
export { AwsDynamoDatabaseService };
|
|
118
|
+
//# sourceMappingURL=aws-dynamodb.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws-dynamodb.mjs","names":["#client","doc: Record<string, unknown>","updateExpr: string[]","exprAttrValues: Record<string, Dynamo.AttributeValue>"],"sources":["../src/adapters/aws-dynamodb.ts"],"sourcesContent":["// oxlint-disable id-length\n\nimport * as Dynamo from \"@aws-sdk/client-dynamodb\";\nimport {\n DatabaseAdapterErrors,\n type DatabaseAdapter,\n type DatabaseAdapterOptions,\n type DatabaseDocumentListOptions,\n type StoryBookerDatabaseDocument,\n} from \"./_internal/database.ts\";\n\nexport class AwsDynamoDatabaseService implements DatabaseAdapter {\n #client: Dynamo.DynamoDBClient;\n\n constructor(client: Dynamo.DynamoDBClient) {\n this.#client = client;\n }\n\n metadata: DatabaseAdapter[\"metadata\"] = { name: \"AWS DynamoDB\" };\n\n listCollections: DatabaseAdapter[\"listCollections\"] = async (options) => {\n const response = await this.#client.send(new Dynamo.ListTablesCommand({}), {\n abortSignal: options.abortSignal,\n });\n return response.TableNames ?? [];\n };\n\n createCollection: DatabaseAdapter[\"createCollection\"] = async (collectionId, options) => {\n try {\n await this.#client.send(\n new Dynamo.CreateTableCommand({\n AttributeDefinitions: [{ AttributeName: \"id\", AttributeType: \"S\" }],\n BillingMode: \"PAY_PER_REQUEST\",\n KeySchema: [{ AttributeName: \"id\", KeyType: \"HASH\" }],\n TableName: collectionId,\n }),\n { abortSignal: options.abortSignal },\n );\n } catch (error) {\n throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId, error);\n }\n };\n\n hasCollection: DatabaseAdapter[\"hasCollection\"] = async (collectionId, options) => {\n try {\n const response = await this.#client.send(\n new Dynamo.DescribeTableCommand({ TableName: collectionId }),\n { abortSignal: options.abortSignal },\n );\n return Boolean(response.Table);\n } catch {\n return false;\n }\n };\n\n deleteCollection: DatabaseAdapter[\"deleteCollection\"] = async (collectionId, options) => {\n try {\n await this.#client.send(new Dynamo.DeleteTableCommand({ TableName: collectionId }), {\n abortSignal: options.abortSignal,\n });\n } catch (error) {\n throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);\n }\n };\n\n listDocuments: DatabaseAdapter[\"listDocuments\"] = async <\n Document extends StoryBookerDatabaseDocument,\n >(\n collectionId: string,\n _listOptions: DatabaseDocumentListOptions<Document>,\n options: DatabaseAdapterOptions,\n ) => {\n const response = await this.#client.send(new Dynamo.ScanCommand({ TableName: collectionId }), {\n abortSignal: options.abortSignal,\n });\n return (response.Items ?? []).map((item) => {\n const doc: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(item)) {\n doc[key] = value.S ?? value.N ?? value.BOOL ?? value.NULL ?? value;\n }\n\n return doc as Document;\n });\n };\n\n getDocument: DatabaseAdapter[\"getDocument\"] = async <\n Document extends StoryBookerDatabaseDocument,\n >(\n collectionId: string,\n documentId: string,\n options: DatabaseAdapterOptions,\n ): Promise<Document> => {\n try {\n const response = await this.#client.send(\n new Dynamo.GetItemCommand({\n Key: { id: { S: documentId } },\n TableName: collectionId,\n }),\n { abortSignal: options.abortSignal },\n );\n const document = response.Item\n ? (Object.fromEntries(\n Object.entries(response.Item).map(([key, value]) => [\n key,\n value.S ?? value.N ?? value.BOOL ?? value.NULL ?? value,\n ]),\n ) as Record<string, unknown>)\n : undefined;\n\n if (!document) {\n throw new Error(\"Document not found\");\n }\n\n document[\"id\"] = documentId;\n return document as Document;\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n\n createDocument: DatabaseAdapter[\"createDocument\"] = async (\n collectionId,\n documentData,\n options,\n ) => {\n try {\n await this.#client.send(\n new Dynamo.PutItemCommand({\n Item: Object.fromEntries(\n Object.entries(documentData).map(([key, value]) => [key, { S: String(value) }]),\n ),\n TableName: collectionId,\n }),\n { abortSignal: options.abortSignal },\n );\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentAlreadyExistsError(\n collectionId,\n documentData.id,\n error,\n );\n }\n };\n\n hasDocument: DatabaseAdapter[\"hasDocument\"] = async (collectionId, documentId, options) => {\n const response = await this.#client.send(\n new Dynamo.GetItemCommand({\n Key: { id: { S: documentId } },\n TableName: collectionId,\n }),\n { abortSignal: options.abortSignal },\n );\n return Boolean(response.Item);\n };\n\n deleteDocument: DatabaseAdapter[\"deleteDocument\"] = async (collectionId, documentId, options) => {\n try {\n await this.#client.send(\n new Dynamo.DeleteItemCommand({\n Key: { id: { S: documentId } },\n TableName: collectionId,\n }),\n { abortSignal: options.abortSignal },\n );\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n\n // oxlint-disable-next-line max-params\n updateDocument: DatabaseAdapter[\"updateDocument\"] = async (\n collectionId,\n documentId,\n documentData,\n options,\n ) => {\n const updateExpr: string[] = [];\n const exprAttrValues: Record<string, Dynamo.AttributeValue> = {};\n for (const [key, value] of Object.entries(documentData)) {\n updateExpr.push(`#${key} = :${key}`);\n exprAttrValues[`:${key}`] = { S: String(value) };\n }\n\n try {\n await this.#client.send(\n new Dynamo.UpdateItemCommand({\n ExpressionAttributeNames: Object.fromEntries(\n Object.keys(documentData).map((k) => [`#${k}`, k]),\n ),\n ExpressionAttributeValues: exprAttrValues,\n Key: { id: { S: documentId } },\n TableName: collectionId,\n UpdateExpression: `SET ${updateExpr.join(\", \")}`,\n }),\n { abortSignal: options.abortSignal },\n );\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StorageAdapter } from "./~internal/adapter/storage.mjs";
|
|
2
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/aws-s3.d.ts
|
|
5
|
+
declare class AwsS3StorageService implements StorageAdapter {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(client: S3Client);
|
|
8
|
+
metadata: StorageAdapter["metadata"];
|
|
9
|
+
createContainer: StorageAdapter["createContainer"];
|
|
10
|
+
deleteContainer: StorageAdapter["deleteContainer"];
|
|
11
|
+
hasContainer: StorageAdapter["hasContainer"];
|
|
12
|
+
listContainers: StorageAdapter["listContainers"];
|
|
13
|
+
deleteFiles: StorageAdapter["deleteFiles"];
|
|
14
|
+
uploadFiles: StorageAdapter["uploadFiles"];
|
|
15
|
+
hasFile: StorageAdapter["hasFile"];
|
|
16
|
+
downloadFile: StorageAdapter["downloadFile"];
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { AwsS3StorageService };
|
|
20
|
+
//# sourceMappingURL=aws-s3.d.mts.map
|
package/dist/aws-s3.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { StorageAdapterErrors } from "./~internal/adapter/storage.mjs";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { CreateBucketCommand, DeleteBucketCommand, DeleteObjectsCommand, GetObjectCommand, HeadObjectCommand, ListBucketsCommand, ListObjectsV2Command, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
4
|
+
|
|
5
|
+
//#region src/adapters/aws-s3.ts
|
|
6
|
+
var AwsS3StorageService = class {
|
|
7
|
+
#client;
|
|
8
|
+
constructor(client) {
|
|
9
|
+
this.#client = client;
|
|
10
|
+
}
|
|
11
|
+
metadata = { name: "AWS S3" };
|
|
12
|
+
createContainer = async (containerId, options) => {
|
|
13
|
+
try {
|
|
14
|
+
await this.#client.send(new CreateBucketCommand({ Bucket: genBucketNameFromContainerId(containerId) }), { abortSignal: options.abortSignal });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
throw new StorageAdapterErrors.ContainerAlreadyExistsError(containerId, error);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
deleteContainer = async (containerId, options) => {
|
|
20
|
+
try {
|
|
21
|
+
await this.#client.send(new DeleteBucketCommand({ Bucket: genBucketNameFromContainerId(containerId) }), { abortSignal: options.abortSignal });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
throw new StorageAdapterErrors.ContainerDoesNotExistError(containerId, error);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
hasContainer = async (containerId, options) => {
|
|
27
|
+
const buckets = await this.#client.send(new ListBucketsCommand({}), { abortSignal: options.abortSignal });
|
|
28
|
+
return Boolean(buckets.Buckets?.some((bucket) => bucket.Name === genBucketNameFromContainerId(containerId)));
|
|
29
|
+
};
|
|
30
|
+
listContainers = async (options) => {
|
|
31
|
+
return (await this.#client.send(new ListBucketsCommand({}), { abortSignal: options.abortSignal })).Buckets?.map((bucket) => bucket.Name) ?? [];
|
|
32
|
+
};
|
|
33
|
+
deleteFiles = async (containerId, filePathsOrPrefix, options) => {
|
|
34
|
+
try {
|
|
35
|
+
const bucket = genBucketNameFromContainerId(containerId);
|
|
36
|
+
let objects = [];
|
|
37
|
+
if (typeof filePathsOrPrefix === "string") objects = ((await this.#client.send(new ListObjectsV2Command({
|
|
38
|
+
Bucket: bucket,
|
|
39
|
+
Prefix: filePathsOrPrefix
|
|
40
|
+
}), { abortSignal: options.abortSignal })).Contents ?? []).map((obj) => ({ Key: obj.Key }));
|
|
41
|
+
else objects = filePathsOrPrefix.map((path) => ({ Key: path }));
|
|
42
|
+
if (objects.length === 0) return;
|
|
43
|
+
await this.#client.send(new DeleteObjectsCommand({
|
|
44
|
+
Bucket: bucket,
|
|
45
|
+
Delete: { Objects: objects }
|
|
46
|
+
}), { abortSignal: options.abortSignal });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new StorageAdapterErrors.CustomError(void 0, `Failed to delete files in container ${containerId}.`, error);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
uploadFiles = async (containerId, files, options) => {
|
|
52
|
+
const bucket = genBucketNameFromContainerId(containerId);
|
|
53
|
+
const promises = files.map(async ({ content, path, mimeType }) => {
|
|
54
|
+
await this.#client.send(new PutObjectCommand({
|
|
55
|
+
Body: typeof content === "string" ? Buffer.from(content) : content,
|
|
56
|
+
Bucket: bucket,
|
|
57
|
+
ContentType: mimeType,
|
|
58
|
+
Key: path
|
|
59
|
+
}), { abortSignal: options.abortSignal }).then((error) => {
|
|
60
|
+
options.logger.error(`Failed to upload file ${path} to bucket ${bucket}:`, error);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
await Promise.allSettled(promises);
|
|
64
|
+
};
|
|
65
|
+
hasFile = async (containerId, filepath, options) => {
|
|
66
|
+
try {
|
|
67
|
+
await this.#client.send(new HeadObjectCommand({
|
|
68
|
+
Bucket: genBucketNameFromContainerId(containerId),
|
|
69
|
+
Key: filepath
|
|
70
|
+
}), { abortSignal: options.abortSignal });
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
downloadFile = async (containerId, filepath, options) => {
|
|
77
|
+
const bucket = genBucketNameFromContainerId(containerId);
|
|
78
|
+
const resp = await this.#client.send(new GetObjectCommand({
|
|
79
|
+
Bucket: bucket,
|
|
80
|
+
Key: filepath
|
|
81
|
+
}), { abortSignal: options.abortSignal });
|
|
82
|
+
if (!resp.Body) throw new StorageAdapterErrors.FileDoesNotExistError(containerId, filepath);
|
|
83
|
+
return {
|
|
84
|
+
content: resp.Body,
|
|
85
|
+
mimeType: resp.ContentType,
|
|
86
|
+
path: filepath
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
function genBucketNameFromContainerId(containerId) {
|
|
91
|
+
return containerId.replaceAll(/[^\w-]+/g, "-").slice(0, 63).toLowerCase();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
export { AwsS3StorageService };
|
|
96
|
+
//# sourceMappingURL=aws-s3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws-s3.mjs","names":["#client","objects: { Key: string }[]"],"sources":["../src/adapters/aws-s3.ts"],"sourcesContent":["import {\n type S3Client,\n CreateBucketCommand,\n DeleteBucketCommand,\n ListBucketsCommand,\n ListObjectsV2Command,\n DeleteObjectsCommand,\n PutObjectCommand,\n HeadObjectCommand,\n GetObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport { Buffer } from \"node:buffer\";\nimport { StorageAdapterErrors, type StorageAdapter } from \"./_internal/storage.ts\";\n\nexport class AwsS3StorageService implements StorageAdapter {\n #client: S3Client;\n\n constructor(client: S3Client) {\n this.#client = client;\n }\n\n metadata: StorageAdapter[\"metadata\"] = { name: \"AWS S3\" };\n\n createContainer: StorageAdapter[\"createContainer\"] = async (containerId, options) => {\n try {\n await this.#client.send(\n new CreateBucketCommand({ Bucket: genBucketNameFromContainerId(containerId) }),\n { abortSignal: options.abortSignal },\n );\n } catch (error) {\n throw new StorageAdapterErrors.ContainerAlreadyExistsError(containerId, error);\n }\n };\n\n deleteContainer: StorageAdapter[\"deleteContainer\"] = async (containerId, options) => {\n try {\n await this.#client.send(\n new DeleteBucketCommand({ Bucket: genBucketNameFromContainerId(containerId) }),\n { abortSignal: options.abortSignal },\n );\n } catch (error) {\n throw new StorageAdapterErrors.ContainerDoesNotExistError(containerId, error);\n }\n };\n\n hasContainer: StorageAdapter[\"hasContainer\"] = async (containerId, options) => {\n const buckets = await this.#client.send(new ListBucketsCommand({}), {\n abortSignal: options.abortSignal,\n });\n return Boolean(\n buckets.Buckets?.some((bucket) => bucket.Name === genBucketNameFromContainerId(containerId)),\n );\n };\n\n listContainers: StorageAdapter[\"listContainers\"] = async (options) => {\n const buckets = await this.#client.send(new ListBucketsCommand({}), {\n abortSignal: options.abortSignal,\n });\n // oxlint-disable-next-line no-non-null-assertion\n return buckets.Buckets?.map((bucket) => bucket.Name!) ?? [];\n };\n\n deleteFiles: StorageAdapter[\"deleteFiles\"] = async (containerId, filePathsOrPrefix, options) => {\n try {\n const bucket = genBucketNameFromContainerId(containerId);\n let objects: { Key: string }[] = [];\n if (typeof filePathsOrPrefix === \"string\") {\n const resp = await this.#client.send(\n new ListObjectsV2Command({\n Bucket: bucket,\n Prefix: filePathsOrPrefix,\n }),\n { abortSignal: options.abortSignal },\n );\n // oxlint-disable-next-line no-non-null-assertion\n objects = (resp.Contents ?? []).map((obj) => ({ Key: obj.Key! }));\n } else {\n objects = filePathsOrPrefix.map((path) => ({ Key: path }));\n }\n if (objects.length === 0) {\n return;\n }\n\n await this.#client.send(\n new DeleteObjectsCommand({\n Bucket: bucket,\n Delete: { Objects: objects },\n }),\n { abortSignal: options.abortSignal },\n );\n } catch (error) {\n throw new StorageAdapterErrors.CustomError(\n undefined,\n `Failed to delete files in container ${containerId}.`,\n error,\n );\n }\n };\n\n uploadFiles: StorageAdapter[\"uploadFiles\"] = async (containerId, files, options) => {\n const bucket = genBucketNameFromContainerId(containerId);\n\n const promises = files.map(async ({ content, path, mimeType }) => {\n await this.#client\n .send(\n new PutObjectCommand({\n Body: typeof content === \"string\" ? Buffer.from(content) : content,\n Bucket: bucket,\n ContentType: mimeType,\n Key: path,\n }),\n { abortSignal: options.abortSignal },\n )\n .then((error: unknown) => {\n options.logger.error(`Failed to upload file ${path} to bucket ${bucket}:`, error);\n });\n });\n\n await Promise.allSettled(promises);\n };\n\n hasFile: StorageAdapter[\"hasFile\"] = async (containerId, filepath, options) => {\n try {\n await this.#client.send(\n new HeadObjectCommand({\n Bucket: genBucketNameFromContainerId(containerId),\n Key: filepath,\n }),\n { abortSignal: options.abortSignal },\n );\n return true;\n } catch {\n return false;\n }\n };\n\n downloadFile: StorageAdapter[\"downloadFile\"] = async (containerId, filepath, options) => {\n const bucket = genBucketNameFromContainerId(containerId);\n const resp = await this.#client.send(new GetObjectCommand({ Bucket: bucket, Key: filepath }), {\n abortSignal: options.abortSignal,\n });\n\n if (!resp.Body) {\n throw new StorageAdapterErrors.FileDoesNotExistError(containerId, filepath);\n }\n\n return {\n content: resp.Body as ReadableStream,\n mimeType: resp.ContentType,\n path: filepath,\n };\n };\n}\n\nfunction genBucketNameFromContainerId(containerId: string): string {\n return containerId\n .replaceAll(/[^\\w-]+/g, \"-\")\n .slice(0, 63)\n .toLowerCase();\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StorageAdapter } from "./~internal/adapter/storage.mjs";
|
|
2
|
+
import { BlobServiceClient } from "@azure/storage-blob";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/azure-blob-storage.d.ts
|
|
5
|
+
declare class AzureBlobStorageService implements StorageAdapter {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(client: BlobServiceClient);
|
|
8
|
+
metadata: StorageAdapter["metadata"];
|
|
9
|
+
createContainer: StorageAdapter["createContainer"];
|
|
10
|
+
deleteContainer: StorageAdapter["deleteContainer"];
|
|
11
|
+
hasContainer: StorageAdapter["hasContainer"];
|
|
12
|
+
listContainers: StorageAdapter["listContainers"];
|
|
13
|
+
deleteFiles: StorageAdapter["deleteFiles"];
|
|
14
|
+
uploadFiles: StorageAdapter["uploadFiles"];
|
|
15
|
+
hasFile: StorageAdapter["hasFile"];
|
|
16
|
+
downloadFile: StorageAdapter["downloadFile"];
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { AzureBlobStorageService };
|
|
20
|
+
//# sourceMappingURL=azure-blob-storage.d.mts.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { StorageAdapterErrors } from "./~internal/adapter/storage.mjs";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/azure-blob-storage.ts
|
|
5
|
+
var AzureBlobStorageService = class {
|
|
6
|
+
#client;
|
|
7
|
+
constructor(client) {
|
|
8
|
+
this.#client = client;
|
|
9
|
+
}
|
|
10
|
+
metadata = { name: "Azure Blob Storage" };
|
|
11
|
+
createContainer = async (containerId, options) => {
|
|
12
|
+
try {
|
|
13
|
+
const containerName = genContainerNameFromContainerId(containerId);
|
|
14
|
+
await this.#client.createContainer(containerName, { abortSignal: options.abortSignal });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
throw new StorageAdapterErrors.ContainerAlreadyExistsError(containerId, error);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
deleteContainer = async (containerId, options) => {
|
|
20
|
+
try {
|
|
21
|
+
const containerName = genContainerNameFromContainerId(containerId);
|
|
22
|
+
await this.#client.getContainerClient(containerName).deleteIfExists({ abortSignal: options.abortSignal });
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw new StorageAdapterErrors.ContainerDoesNotExistError(containerId, error);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
hasContainer = async (containerId, options) => {
|
|
28
|
+
const containerName = genContainerNameFromContainerId(containerId);
|
|
29
|
+
return await this.#client.getContainerClient(containerName).exists({ abortSignal: options.abortSignal });
|
|
30
|
+
};
|
|
31
|
+
listContainers = async (options) => {
|
|
32
|
+
const containers = [];
|
|
33
|
+
for await (const item of this.#client.listContainers({ abortSignal: options.abortSignal })) containers.push(item.name);
|
|
34
|
+
return containers;
|
|
35
|
+
};
|
|
36
|
+
deleteFiles = async (containerId, filePathsOrPrefix, options) => {
|
|
37
|
+
const containerName = genContainerNameFromContainerId(containerId);
|
|
38
|
+
const containerClient = this.#client.getContainerClient(containerName);
|
|
39
|
+
const blobClientsToDelete = [];
|
|
40
|
+
if (typeof filePathsOrPrefix === "string") for await (const blob of containerClient.listBlobsFlat({
|
|
41
|
+
abortSignal: options.abortSignal,
|
|
42
|
+
prefix: filePathsOrPrefix
|
|
43
|
+
})) blobClientsToDelete.push(containerClient.getBlobClient(blob.name));
|
|
44
|
+
else for (const filepath of filePathsOrPrefix) blobClientsToDelete.push(containerClient.getBlobClient(filepath));
|
|
45
|
+
if (blobClientsToDelete.length === 0) return;
|
|
46
|
+
const response = await containerClient.getBlobBatchClient().deleteBlobs(blobClientsToDelete, { abortSignal: options.abortSignal });
|
|
47
|
+
if (response.errorCode) throw new StorageAdapterErrors.CustomError(void 0, `Failed to delete ${response.subResponsesFailedCount} blobs in container ${containerId}: ${response.errorCode}`);
|
|
48
|
+
};
|
|
49
|
+
uploadFiles = async (containerId, files, options) => {
|
|
50
|
+
const containerName = genContainerNameFromContainerId(containerId);
|
|
51
|
+
const containerClient = this.#client.getContainerClient(containerName);
|
|
52
|
+
const { errors } = await promisePool(files.map(({ content, path, mimeType }) => async () => {
|
|
53
|
+
await uploadFileToBlobStorage(containerClient.getBlockBlobClient(path), content, mimeType, options.abortSignal);
|
|
54
|
+
}), 20);
|
|
55
|
+
if (errors.length > 0) options.logger.error(`Failed to upload ${errors.length} files. Errors:`, errors);
|
|
56
|
+
};
|
|
57
|
+
hasFile = async (containerId, filepath, options) => {
|
|
58
|
+
const containerName = genContainerNameFromContainerId(containerId);
|
|
59
|
+
return await this.#client.getContainerClient(containerName).getBlockBlobClient(filepath).exists({ abortSignal: options.abortSignal });
|
|
60
|
+
};
|
|
61
|
+
downloadFile = async (containerId, filepath, options) => {
|
|
62
|
+
const containerName = genContainerNameFromContainerId(containerId);
|
|
63
|
+
const blockBlobClient = this.#client.getContainerClient(containerName).getBlockBlobClient(filepath);
|
|
64
|
+
if (!await blockBlobClient.exists()) throw new StorageAdapterErrors.FileDoesNotExistError(containerId, filepath);
|
|
65
|
+
const downloadResponse = await blockBlobClient.download(0, void 0, { abortSignal: options.abortSignal });
|
|
66
|
+
if (!downloadResponse.readableStreamBody) throw new StorageAdapterErrors.FileMalformedError(containerId, filepath, "No readable stream body found.");
|
|
67
|
+
return {
|
|
68
|
+
content: downloadResponse.readableStreamBody,
|
|
69
|
+
mimeType: downloadResponse.contentType,
|
|
70
|
+
path: filepath
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
function genContainerNameFromContainerId(containerId) {
|
|
75
|
+
return containerId.replaceAll(/[^\w-]+/g, "-").slice(0, 255).toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
async function uploadFileToBlobStorage(client, data, mimeType, abortSignal) {
|
|
78
|
+
if (typeof data === "string") {
|
|
79
|
+
const blob = new Blob([data], { type: mimeType });
|
|
80
|
+
await client.uploadData(blob, {
|
|
81
|
+
abortSignal,
|
|
82
|
+
blobHTTPHeaders: { blobContentType: mimeType }
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (data instanceof Blob) {
|
|
87
|
+
await client.uploadData(data, {
|
|
88
|
+
abortSignal,
|
|
89
|
+
blobHTTPHeaders: { blobContentType: mimeType }
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (data instanceof ReadableStream) {
|
|
94
|
+
const stream = data;
|
|
95
|
+
await client.uploadStream(Readable.fromWeb(stream), void 0, void 0, {
|
|
96
|
+
abortSignal,
|
|
97
|
+
blobHTTPHeaders: { blobContentType: mimeType }
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`Unknown file type`);
|
|
102
|
+
}
|
|
103
|
+
async function promisePool(tasks, concurrencyLimit) {
|
|
104
|
+
const promises = [];
|
|
105
|
+
const errors = [];
|
|
106
|
+
const executing = /* @__PURE__ */ new Set();
|
|
107
|
+
for (const task of tasks) {
|
|
108
|
+
const promise = Promise.resolve().then(task);
|
|
109
|
+
promises.push(promise);
|
|
110
|
+
executing.add(promise);
|
|
111
|
+
const cleanup = () => executing.delete(promise);
|
|
112
|
+
promise.then(cleanup).catch((error) => {
|
|
113
|
+
errors.push(error);
|
|
114
|
+
cleanup();
|
|
115
|
+
});
|
|
116
|
+
if (executing.size >= concurrencyLimit) await Promise.race(executing);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
errors,
|
|
120
|
+
results: await Promise.all(promises)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
export { AzureBlobStorageService };
|
|
126
|
+
//# sourceMappingURL=azure-blob-storage.mjs.map
|