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.
Files changed (215) hide show
  1. package/README.md +40 -18
  2. package/dist/adapters/_internal/queue.d.mts +127 -0
  3. package/dist/aws-dynamodb.d.mts +22 -0
  4. package/dist/aws-dynamodb.mjs +118 -0
  5. package/dist/aws-dynamodb.mjs.map +1 -0
  6. package/dist/aws-s3.d.mts +20 -0
  7. package/dist/aws-s3.mjs +96 -0
  8. package/dist/aws-s3.mjs.map +1 -0
  9. package/dist/azure-blob-storage.d.mts +20 -0
  10. package/dist/azure-blob-storage.mjs +126 -0
  11. package/dist/azure-blob-storage.mjs.map +1 -0
  12. package/dist/azure-cosmos-db.d.mts +23 -0
  13. package/dist/azure-cosmos-db.mjs +87 -0
  14. package/dist/azure-cosmos-db.mjs.map +1 -0
  15. package/dist/azure-data-tables.d.mts +23 -0
  16. package/dist/azure-data-tables.mjs +127 -0
  17. package/dist/azure-data-tables.mjs.map +1 -0
  18. package/dist/azure-easy-auth.d.mts +50 -0
  19. package/dist/azure-easy-auth.mjs +88 -0
  20. package/dist/azure-easy-auth.mjs.map +1 -0
  21. package/dist/azure-functions.d.mts +62 -0
  22. package/dist/azure-functions.mjs +147 -0
  23. package/dist/azure-functions.mjs.map +1 -0
  24. package/dist/fs.d.mts +37 -0
  25. package/dist/fs.mjs +240 -0
  26. package/dist/fs.mjs.map +1 -0
  27. package/dist/gcp-big-table.d.mts +23 -0
  28. package/dist/gcp-big-table.mjs +92 -0
  29. package/dist/gcp-big-table.mjs.map +1 -0
  30. package/dist/gcp-firestore.d.mts +22 -0
  31. package/dist/gcp-firestore.mjs +87 -0
  32. package/dist/gcp-firestore.mjs.map +1 -0
  33. package/dist/gcp-storage.d.mts +20 -0
  34. package/dist/gcp-storage.mjs +96 -0
  35. package/dist/gcp-storage.mjs.map +1 -0
  36. package/dist/handlers/handle-process-zip.mjs +90 -0
  37. package/dist/handlers/handle-process-zip.mjs.map +1 -0
  38. package/dist/handlers/handle-purge.d.mts +12 -0
  39. package/dist/handlers/handle-purge.mjs +36 -0
  40. package/dist/handlers/handle-purge.mjs.map +1 -0
  41. package/dist/handlers/handle-serve-storybook.mjs +94 -0
  42. package/dist/handlers/handle-serve-storybook.mjs.map +1 -0
  43. package/dist/index.d.mts +28 -0
  44. package/dist/index.mjs +62 -0
  45. package/dist/index.mjs.map +1 -0
  46. package/dist/models/builds-model.mjs +248 -0
  47. package/dist/models/builds-model.mjs.map +1 -0
  48. package/dist/models/builds-schema.d.mts +171 -0
  49. package/dist/models/builds-schema.mjs +67 -0
  50. package/dist/models/builds-schema.mjs.map +1 -0
  51. package/dist/models/projects-model.mjs +122 -0
  52. package/dist/models/projects-model.mjs.map +1 -0
  53. package/dist/models/projects-schema.d.mts +70 -0
  54. package/dist/models/projects-schema.mjs +37 -0
  55. package/dist/models/projects-schema.mjs.map +1 -0
  56. package/dist/models/tags-model.mjs +110 -0
  57. package/dist/models/tags-model.mjs.map +1 -0
  58. package/dist/models/tags-schema.d.mts +76 -0
  59. package/dist/models/tags-schema.mjs +34 -0
  60. package/dist/models/tags-schema.mjs.map +1 -0
  61. package/dist/models/~model.mjs +43 -0
  62. package/dist/models/~model.mjs.map +1 -0
  63. package/dist/models/~shared-schema.d.mts +1 -0
  64. package/dist/models/~shared-schema.mjs +20 -0
  65. package/dist/models/~shared-schema.mjs.map +1 -0
  66. package/dist/mysql.d.mts +39 -0
  67. package/dist/mysql.mjs +151 -0
  68. package/dist/mysql.mjs.map +1 -0
  69. package/dist/redis.d.mts +33 -0
  70. package/dist/redis.mjs +118 -0
  71. package/dist/redis.mjs.map +1 -0
  72. package/dist/routers/account-router.mjs +91 -0
  73. package/dist/routers/account-router.mjs.map +1 -0
  74. package/dist/routers/builds-router.mjs +347 -0
  75. package/dist/routers/builds-router.mjs.map +1 -0
  76. package/dist/routers/projects-router.mjs +236 -0
  77. package/dist/routers/projects-router.mjs.map +1 -0
  78. package/dist/routers/root-router.mjs +108 -0
  79. package/dist/routers/root-router.mjs.map +1 -0
  80. package/dist/routers/tags-router.mjs +269 -0
  81. package/dist/routers/tags-router.mjs.map +1 -0
  82. package/dist/routers/tasks-router.mjs +71 -0
  83. package/dist/routers/tasks-router.mjs.map +1 -0
  84. package/dist/urls.d.mts +47 -0
  85. package/dist/urls.mjs +208 -0
  86. package/dist/urls.mjs.map +1 -0
  87. package/dist/utils/adapter-utils.d.mts +14 -0
  88. package/dist/utils/adapter-utils.mjs +14 -0
  89. package/dist/utils/adapter-utils.mjs.map +1 -0
  90. package/dist/utils/auth.mjs +25 -0
  91. package/dist/utils/auth.mjs.map +1 -0
  92. package/dist/utils/error.d.mts +21 -0
  93. package/dist/utils/error.mjs +109 -0
  94. package/dist/utils/error.mjs.map +1 -0
  95. package/dist/utils/file-utils.mjs +16 -0
  96. package/dist/utils/file-utils.mjs.map +1 -0
  97. package/dist/utils/openapi-utils.mjs +45 -0
  98. package/dist/utils/openapi-utils.mjs.map +1 -0
  99. package/dist/utils/request.mjs +35 -0
  100. package/dist/utils/request.mjs.map +1 -0
  101. package/dist/utils/response.mjs +24 -0
  102. package/dist/utils/response.mjs.map +1 -0
  103. package/dist/utils/store.mjs +54 -0
  104. package/dist/utils/store.mjs.map +1 -0
  105. package/dist/utils/ui-utils.mjs +38 -0
  106. package/dist/utils/ui-utils.mjs.map +1 -0
  107. package/dist/utils/url-utils.d.mts +10 -0
  108. package/dist/utils/url-utils.mjs +54 -0
  109. package/dist/utils/url-utils.mjs.map +1 -0
  110. package/dist/~internal/adapter/auth.d.mts +123 -0
  111. package/dist/~internal/adapter/auth.mjs +20 -0
  112. package/dist/~internal/adapter/auth.mjs.map +1 -0
  113. package/dist/~internal/adapter/database.d.mts +240 -0
  114. package/dist/~internal/adapter/database.mjs +63 -0
  115. package/dist/~internal/adapter/database.mjs.map +1 -0
  116. package/dist/~internal/adapter/logger.d.mts +34 -0
  117. package/dist/~internal/adapter/logger.mjs +13 -0
  118. package/dist/~internal/adapter/logger.mjs.map +1 -0
  119. package/dist/~internal/adapter/storage.d.mts +208 -0
  120. package/dist/~internal/adapter/storage.mjs +63 -0
  121. package/dist/~internal/adapter/storage.mjs.map +1 -0
  122. package/dist/~internal/adapter/ui.d.mts +109 -0
  123. package/dist/~internal/adapter/ui.mjs +1 -0
  124. package/dist/~internal/adapter.d.mts +8 -0
  125. package/dist/~internal/adapter.mjs +6 -0
  126. package/dist/~internal/constants.d.mts +24 -0
  127. package/dist/~internal/constants.mjs +32 -0
  128. package/dist/~internal/constants.mjs.map +1 -0
  129. package/dist/~internal/mimes.d.mts +449 -0
  130. package/dist/~internal/mimes.mjs +454 -0
  131. package/dist/~internal/mimes.mjs.map +1 -0
  132. package/dist/~internal/router.d.mts +1651 -0
  133. package/dist/~internal/router.mjs +39 -0
  134. package/dist/~internal/router.mjs.map +1 -0
  135. package/dist/~internal/types.d.mts +77 -0
  136. package/dist/~internal/types.mjs +1 -0
  137. package/dist/~internal/utils.d.mts +4 -0
  138. package/dist/~internal/utils.mjs +5 -0
  139. package/openapi.json +3162 -0
  140. package/package.json +148 -27
  141. package/src/adapters/_internal/auth.ts +135 -0
  142. package/src/adapters/_internal/database.ts +241 -0
  143. package/src/adapters/_internal/index.ts +8 -0
  144. package/src/adapters/_internal/logger.ts +41 -0
  145. package/src/adapters/_internal/queue.ts +151 -0
  146. package/src/adapters/_internal/storage.ts +197 -0
  147. package/src/adapters/_internal/ui.ts +103 -0
  148. package/src/adapters/aws-dynamodb.ts +201 -0
  149. package/src/adapters/aws-s3.ts +160 -0
  150. package/src/adapters/azure-blob-storage.ts +223 -0
  151. package/src/adapters/azure-cosmos-db.ts +158 -0
  152. package/src/adapters/azure-data-tables.ts +223 -0
  153. package/src/adapters/azure-easy-auth.ts +174 -0
  154. package/src/adapters/azure-functions.ts +242 -0
  155. package/src/adapters/fs.ts +398 -0
  156. package/src/adapters/gcp-big-table.ts +157 -0
  157. package/src/adapters/gcp-firestore.ts +146 -0
  158. package/src/adapters/gcp-storage.ts +141 -0
  159. package/src/adapters/mysql.ts +296 -0
  160. package/src/adapters/redis.ts +242 -0
  161. package/src/handlers/handle-process-zip.ts +117 -0
  162. package/src/handlers/handle-purge.ts +65 -0
  163. package/src/handlers/handle-serve-storybook.ts +101 -0
  164. package/src/index.ts +81 -16
  165. package/src/mocks/mock-auth-service.ts +51 -0
  166. package/src/mocks/mock-store.ts +26 -0
  167. package/src/models/builds-model.ts +373 -0
  168. package/src/models/builds-schema.ts +84 -0
  169. package/src/models/projects-model.ts +177 -0
  170. package/src/models/projects-schema.ts +69 -0
  171. package/src/models/tags-model.ts +138 -0
  172. package/src/models/tags-schema.ts +45 -0
  173. package/src/models/~model.ts +79 -0
  174. package/src/models/~shared-schema.ts +14 -0
  175. package/src/routers/_app-router.ts +57 -0
  176. package/src/routers/account-router.ts +136 -0
  177. package/src/routers/builds-router.ts +464 -0
  178. package/src/routers/projects-router.ts +309 -0
  179. package/src/routers/root-router.ts +127 -0
  180. package/src/routers/tags-router.ts +339 -0
  181. package/src/routers/tasks-router.ts +75 -0
  182. package/src/types.ts +107 -0
  183. package/src/urls.ts +327 -0
  184. package/src/utils/adapter-utils.ts +26 -0
  185. package/src/utils/auth.test.ts +71 -0
  186. package/src/utils/auth.ts +39 -0
  187. package/src/utils/constants.ts +31 -0
  188. package/src/utils/date-utils.ts +10 -0
  189. package/src/utils/error.test.ts +86 -0
  190. package/src/utils/error.ts +140 -0
  191. package/src/utils/file-utils.test.ts +65 -0
  192. package/src/utils/file-utils.ts +43 -0
  193. package/src/utils/index.ts +3 -0
  194. package/src/utils/mime-utils.ts +457 -0
  195. package/src/utils/openapi-utils.ts +49 -0
  196. package/src/utils/request.ts +97 -0
  197. package/src/utils/response.ts +20 -0
  198. package/src/utils/store.ts +85 -0
  199. package/src/utils/story-utils.ts +42 -0
  200. package/src/utils/text-utils.ts +10 -0
  201. package/src/utils/ui-utils.ts +57 -0
  202. package/src/utils/url-utils.ts +113 -0
  203. package/dist/index.js +0 -554
  204. package/src/commands/create.ts +0 -263
  205. package/src/commands/purge.ts +0 -70
  206. package/src/commands/test.ts +0 -42
  207. package/src/service-schema.d.ts +0 -2023
  208. package/src/utils/auth-utils.ts +0 -31
  209. package/src/utils/pkg-utils.ts +0 -37
  210. package/src/utils/sb-build.ts +0 -55
  211. package/src/utils/sb-test.ts +0 -115
  212. package/src/utils/schema-utils.ts +0 -123
  213. package/src/utils/stream-utils.ts +0 -72
  214. package/src/utils/types.ts +0 -4
  215. package/src/utils/zip.ts +0 -77
package/README.md CHANGED
@@ -1,27 +1,49 @@
1
- # StoryBooker CLI
1
+ # StoryBooker Core
2
2
 
3
- A NodeJS CLI to sync with StoryBooker service. The CLI can be used to build, test, upload StoryBook to the service.
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
- ## Usage
8
+ ## Running on basic Node server
8
9
 
9
- See commands + help
10
+ > Refer Hono docs: https://hono.dev/docs/getting-started/nodejs
10
11
 
11
- ```sh
12
- npx -y storybooker -h
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
- ### Build+Upload assets
16
-
17
- ```sh
18
- npx -y storybooker create \
19
- -u https://<storybooker-service> \
20
- -p <project-id> \
21
- --id <build-id> \
22
- --test \
23
- -l <branch-name> -l <another-label> \
24
- --authorName <your-name> \
25
- --authorEmail <your-email> \
26
- --message "<readable message>"
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
@@ -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":";;;;;AAcA,IAAa,sBAAb,MAA2D;CACzD;CAEA,YAAY,QAAkB;AAC5B,QAAKA,SAAU;;CAGjB,WAAuC,EAAE,MAAM,UAAU;CAEzD,kBAAqD,OAAO,aAAa,YAAY;AACnF,MAAI;AACF,SAAM,MAAKA,OAAQ,KACjB,IAAI,oBAAoB,EAAE,QAAQ,6BAA6B,YAAY,EAAE,CAAC,EAC9E,EAAE,aAAa,QAAQ,aAAa,CACrC;WACM,OAAO;AACd,SAAM,IAAI,qBAAqB,4BAA4B,aAAa,MAAM;;;CAIlF,kBAAqD,OAAO,aAAa,YAAY;AACnF,MAAI;AACF,SAAM,MAAKA,OAAQ,KACjB,IAAI,oBAAoB,EAAE,QAAQ,6BAA6B,YAAY,EAAE,CAAC,EAC9E,EAAE,aAAa,QAAQ,aAAa,CACrC;WACM,OAAO;AACd,SAAM,IAAI,qBAAqB,2BAA2B,aAAa,MAAM;;;CAIjF,eAA+C,OAAO,aAAa,YAAY;EAC7E,MAAM,UAAU,MAAM,MAAKA,OAAQ,KAAK,IAAI,mBAAmB,EAAE,CAAC,EAAE,EAClE,aAAa,QAAQ,aACtB,CAAC;AACF,SAAO,QACL,QAAQ,SAAS,MAAM,WAAW,OAAO,SAAS,6BAA6B,YAAY,CAAC,CAC7F;;CAGH,iBAAmD,OAAO,YAAY;AAKpE,UAJgB,MAAM,MAAKA,OAAQ,KAAK,IAAI,mBAAmB,EAAE,CAAC,EAAE,EAClE,aAAa,QAAQ,aACtB,CAAC,EAEa,SAAS,KAAK,WAAW,OAAO,KAAM,IAAI,EAAE;;CAG7D,cAA6C,OAAO,aAAa,mBAAmB,YAAY;AAC9F,MAAI;GACF,MAAM,SAAS,6BAA6B,YAAY;GACxD,IAAIC,UAA6B,EAAE;AACnC,OAAI,OAAO,sBAAsB,SAS/B,aARa,MAAM,MAAKD,OAAQ,KAC9B,IAAI,qBAAqB;IACvB,QAAQ;IACR,QAAQ;IACT,CAAC,EACF,EAAE,aAAa,QAAQ,aAAa,CACrC,EAEe,YAAY,EAAE,EAAE,KAAK,SAAS,EAAE,KAAK,IAAI,KAAM,EAAE;OAEjE,WAAU,kBAAkB,KAAK,UAAU,EAAE,KAAK,MAAM,EAAE;AAE5D,OAAI,QAAQ,WAAW,EACrB;AAGF,SAAM,MAAKA,OAAQ,KACjB,IAAI,qBAAqB;IACvB,QAAQ;IACR,QAAQ,EAAE,SAAS,SAAS;IAC7B,CAAC,EACF,EAAE,aAAa,QAAQ,aAAa,CACrC;WACM,OAAO;AACd,SAAM,IAAI,qBAAqB,YAC7B,QACA,uCAAuC,YAAY,IACnD,MACD;;;CAIL,cAA6C,OAAO,aAAa,OAAO,YAAY;EAClF,MAAM,SAAS,6BAA6B,YAAY;EAExD,MAAM,WAAW,MAAM,IAAI,OAAO,EAAE,SAAS,MAAM,eAAe;AAChE,SAAM,MAAKA,OACR,KACC,IAAI,iBAAiB;IACnB,MAAM,OAAO,YAAY,WAAW,OAAO,KAAK,QAAQ,GAAG;IAC3D,QAAQ;IACR,aAAa;IACb,KAAK;IACN,CAAC,EACF,EAAE,aAAa,QAAQ,aAAa,CACrC,CACA,MAAM,UAAmB;AACxB,YAAQ,OAAO,MAAM,yBAAyB,KAAK,aAAa,OAAO,IAAI,MAAM;KACjF;IACJ;AAEF,QAAM,QAAQ,WAAW,SAAS;;CAGpC,UAAqC,OAAO,aAAa,UAAU,YAAY;AAC7E,MAAI;AACF,SAAM,MAAKA,OAAQ,KACjB,IAAI,kBAAkB;IACpB,QAAQ,6BAA6B,YAAY;IACjD,KAAK;IACN,CAAC,EACF,EAAE,aAAa,QAAQ,aAAa,CACrC;AACD,UAAO;UACD;AACN,UAAO;;;CAIX,eAA+C,OAAO,aAAa,UAAU,YAAY;EACvF,MAAM,SAAS,6BAA6B,YAAY;EACxD,MAAM,OAAO,MAAM,MAAKA,OAAQ,KAAK,IAAI,iBAAiB;GAAE,QAAQ;GAAQ,KAAK;GAAU,CAAC,EAAE,EAC5F,aAAa,QAAQ,aACtB,CAAC;AAEF,MAAI,CAAC,KAAK,KACR,OAAM,IAAI,qBAAqB,sBAAsB,aAAa,SAAS;AAG7E,SAAO;GACL,SAAS,KAAK;GACd,UAAU,KAAK;GACf,MAAM;GACP;;;AAIL,SAAS,6BAA6B,aAA6B;AACjE,QAAO,YACJ,WAAW,YAAY,IAAI,CAC3B,MAAM,GAAG,GAAG,CACZ,aAAa"}
@@ -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