storybooker 0.19.4 → 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
@@ -0,0 +1,147 @@
1
+ import { SERVICE_NAME } from "./~internal/constants.mjs";
2
+ import { generatePrefixFromBaseRoute, urlJoin } from "./utils/url-utils.mjs";
3
+ import { createHonoRouter, createPurgeHandler } from "./index.mjs";
4
+
5
+ //#region src/adapters/azure-functions.ts
6
+ const DEFAULT_PURGE_SCHEDULE_CRON = "0 0 0 * * *";
7
+ /**
8
+ * Register the Storybooker router with the Azure Functions App.
9
+ *
10
+ * - It enabled streaming responses for HTTP functions.
11
+ * - It registers the HTTP function with provided route and auth-level.
12
+ * - It registers the Timer function for purge if `purgeScheduleCron` is not `null`.
13
+ *
14
+ * @param app Azure Functions App instance
15
+ * @param options Options for registering the router
16
+ */
17
+ function registerStoryBookerRouter(app, options) {
18
+ app.setup?.({ enableHttpStream: true });
19
+ const { auth, purgeScheduleCron, route, ...rest } = options;
20
+ const routerOptions = rest;
21
+ routerOptions.config ??= {};
22
+ routerOptions.config.errorParser ??= parseAzureRestError;
23
+ if (route) routerOptions.config.prefix = generatePrefixFromBaseRoute(route);
24
+ if (typeof auth === "object") routerOptions.auth = auth;
25
+ const router = createHonoRouter(routerOptions);
26
+ app.http(SERVICE_NAME, {
27
+ authLevel: typeof auth === "string" ? auth : "anonymous",
28
+ handler: async (httpRequest) => {
29
+ const request = newRequestFromAzureFunctions(httpRequest);
30
+ return newAzureFunctionsResponse(await router.fetch(request));
31
+ },
32
+ methods: [
33
+ "GET",
34
+ "POST",
35
+ "DELETE",
36
+ "HEAD",
37
+ "PATCH",
38
+ "PUT",
39
+ "OPTIONS",
40
+ "TRACE",
41
+ "CONNECT"
42
+ ],
43
+ route: urlJoin(route ?? "", "{**path}")
44
+ });
45
+ if (purgeScheduleCron !== null && app.timer) {
46
+ const schedule = purgeScheduleCron ?? DEFAULT_PURGE_SCHEDULE_CRON;
47
+ const purgeHandler = createPurgeHandler({
48
+ database: routerOptions.database,
49
+ storage: routerOptions.storage
50
+ });
51
+ app.timer(`${SERVICE_NAME}-timer_purge`, {
52
+ handler: async (_timer, context) => purgeHandler({}, { logger: createAzureContextLogger(context) }),
53
+ runOnStartup: false,
54
+ schedule
55
+ });
56
+ }
57
+ }
58
+ const parseAzureRestError = (error) => {
59
+ if (error instanceof Error && error.name === "RestError") {
60
+ const restError = error;
61
+ const details = restError.details ?? {};
62
+ const message = details["errorMessage"] ?? restError.message;
63
+ return {
64
+ errorMessage: `${details["errorCode"] ?? restError.name} (${restError.code ?? restError.statusCode}): ${message}`,
65
+ errorStatus: restError.statusCode,
66
+ errorType: "AzureRest"
67
+ };
68
+ }
69
+ };
70
+ function createAzureContextLogger(context) {
71
+ return {
72
+ debug: context.debug.bind(context),
73
+ error: context.error.bind(context),
74
+ log: context.log.bind(context),
75
+ metadata: { name: "Azure Functions" }
76
+ };
77
+ }
78
+ /**
79
+ * Utils (@refer https://github.com/Marplex/hono-azurefunc-adapter/)
80
+ */
81
+ /** */
82
+ function newRequestFromAzureFunctions(request) {
83
+ const hasBody = !["GET", "HEAD"].includes(request.method);
84
+ return new Request(request.url, {
85
+ headers: headersToObject(request.headers),
86
+ method: request.method,
87
+ ...hasBody ? {
88
+ body: request.body,
89
+ duplex: "half"
90
+ } : {}
91
+ });
92
+ }
93
+ function newAzureFunctionsResponse(response) {
94
+ let headers = headersToObject(response.headers);
95
+ let cookies = cookiesFromHeaders(response.headers);
96
+ return {
97
+ body: streamToAsyncIterator(response.body),
98
+ cookies,
99
+ headers,
100
+ status: response.status
101
+ };
102
+ }
103
+ function headersToObject(input) {
104
+ const headers = {};
105
+ input.forEach((value, key) => headers[key] = value);
106
+ return headers;
107
+ }
108
+ function cookiesFromHeaders(headers) {
109
+ const cookies = headers.getSetCookie();
110
+ if (cookies.length === 0) return;
111
+ return cookies.map((cookie) => parseCookieString(cookie));
112
+ }
113
+ function parseCookieString(cookieString) {
114
+ const [first, ...attributesArray] = cookieString.split(";").map((item) => item.split("=")).map(([key, value]) => [key?.trim().toLowerCase(), value ?? "true"]);
115
+ const [name, encodedValue] = first ?? [];
116
+ const attrs = Object.fromEntries(attributesArray);
117
+ return {
118
+ domain: attrs["domain"],
119
+ expires: attrs["expires"] ? new Date(attrs["expires"]) : void 0,
120
+ httpOnly: attrs["httponly"] === "true",
121
+ maxAge: attrs["max-age"] ? Number.parseInt(attrs["max-age"], 10) : void 0,
122
+ name: name ?? "",
123
+ path: attrs["path"],
124
+ sameSite: attrs["samesite"],
125
+ secure: attrs["secure"] === "true",
126
+ value: encodedValue ? decodeURIComponent(encodedValue) : ""
127
+ };
128
+ }
129
+ function streamToAsyncIterator(readable) {
130
+ if (readable === null || !readable) return null;
131
+ const reader = readable.getReader();
132
+ return {
133
+ async next() {
134
+ return await reader.read();
135
+ },
136
+ return() {
137
+ reader.releaseLock();
138
+ },
139
+ [Symbol.asyncIterator]() {
140
+ return this;
141
+ }
142
+ };
143
+ }
144
+
145
+ //#endregion
146
+ export { registerStoryBookerRouter };
147
+ //# sourceMappingURL=azure-functions.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"azure-functions.mjs","names":["routerOptions: RouterOptions<User>","parseAzureRestError: ErrorParser","message: string","headers: Record<string, string>","attrs: Record<string, string>"],"sources":["../src/adapters/azure-functions.ts"],"sourcesContent":["// oxlint-disable max-lines-per-function\n\nimport type { RestError } from \"@azure/core-rest-pipeline\";\nimport type {\n Cookie,\n HttpFunctionOptions,\n HttpRequest,\n HttpResponseInit,\n HttpTriggerOptions,\n InvocationContext,\n SetupOptions,\n TimerFunctionOptions,\n} from \"@azure/functions\";\nimport { createHonoRouter, createPurgeHandler } from \"../index.ts\";\nimport type { ErrorParser, RouterOptions, StoryBookerUser } from \"../types.ts\";\nimport { generatePrefixFromBaseRoute, SERVICE_NAME, urlJoin } from \"../utils/index.ts\";\nimport type { AuthAdapter } from \"./_internal/auth.ts\";\nimport type { LoggerAdapter } from \"./_internal/logger.ts\";\n\nconst DEFAULT_PURGE_SCHEDULE_CRON = \"0 0 0 * * *\";\n\nexport type * from \"storybooker/types\";\n\n/**\n * Minimal representation of Azure Functions App namespace\n * to register HTTP and Timer functions.\n */\ninterface FunctionsApp {\n http(name: string, options: HttpFunctionOptions): void;\n setup?(options: SetupOptions): void;\n timer?(name: string, options: TimerFunctionOptions): void;\n}\n\n/**\n * Options to register the storybooker router\n */\nexport interface RegisterStorybookerRouterOptions<User extends StoryBookerUser> extends Omit<\n RouterOptions<User>,\n \"auth\"\n> {\n /**\n * For authenticating routes, either use an AuthAdapter or Functions auth-level property.\n *\n * - AuthAdapter allows full customization of authentication logic. This will set the function to \"anonymous\" auth-level.\n * - Auth-level is a simpler way to set predefined authentication levels (\"function\", \"admin\").\n * This is a good option to set if the service is used in\n * Headless mode and requires single token authentication for all the requests.\n *\n * @see https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cfunctionsv2&pivots=programming-language-javascript#http-auth (AuthLevels)\n */\n auth?: AuthAdapter<User> | Exclude<HttpTriggerOptions[\"authLevel\"], \"anonymous\">;\n\n /**\n * Define the route on which all router is placed.\n * Can be a sub-path of the main API route.\n *\n * @default ''\n */\n route?: string;\n\n /**\n * Modify the cron-schedule of timer function\n * which purge outdated storybooks.\n *\n * Pass `null` to disable auto-purge functionality.\n *\n * @default \"0 0 0 * * *\" // Every midnight\n */\n purgeScheduleCron?: string | null;\n}\n\n/**\n * Register the Storybooker router with the Azure Functions App.\n *\n * - It enabled streaming responses for HTTP functions.\n * - It registers the HTTP function with provided route and auth-level.\n * - It registers the Timer function for purge if `purgeScheduleCron` is not `null`.\n *\n * @param app Azure Functions App instance\n * @param options Options for registering the router\n */\nexport function registerStoryBookerRouter<User extends StoryBookerUser>(\n app: FunctionsApp,\n options: RegisterStorybookerRouterOptions<User>,\n): void {\n app.setup?.({ enableHttpStream: true });\n const { auth, purgeScheduleCron, route, ...rest } = options;\n\n const routerOptions: RouterOptions<User> = rest;\n routerOptions.config ??= {};\n routerOptions.config.errorParser ??= parseAzureRestError;\n\n if (route) {\n routerOptions.config.prefix = generatePrefixFromBaseRoute(route);\n }\n if (typeof auth === \"object\") {\n routerOptions.auth = auth;\n }\n\n const router = createHonoRouter(routerOptions);\n\n app.http(SERVICE_NAME, {\n authLevel: typeof auth === \"string\" ? auth : \"anonymous\",\n handler: async (httpRequest) => {\n const request = newRequestFromAzureFunctions(httpRequest);\n const response = await router.fetch(request);\n return newAzureFunctionsResponse(response);\n },\n methods: [\"GET\", \"POST\", \"DELETE\", \"HEAD\", \"PATCH\", \"PUT\", \"OPTIONS\", \"TRACE\", \"CONNECT\"],\n route: urlJoin(route ?? \"\", \"{**path}\"),\n });\n\n if (purgeScheduleCron !== null && app.timer) {\n const schedule = purgeScheduleCron ?? DEFAULT_PURGE_SCHEDULE_CRON;\n const purgeHandler = createPurgeHandler({\n database: routerOptions.database,\n storage: routerOptions.storage,\n });\n\n app.timer(`${SERVICE_NAME}-timer_purge`, {\n // oxlint-disable-next-line require-await\n handler: async (_timer, context) =>\n purgeHandler({}, { logger: createAzureContextLogger(context) }),\n runOnStartup: false,\n schedule,\n });\n }\n}\n\nconst parseAzureRestError: ErrorParser = (error) => {\n if (error instanceof Error && error.name === \"RestError\") {\n const restError = error as RestError;\n const details = (restError.details ?? {}) as Record<string, string>;\n const message: string = details[\"errorMessage\"] ?? restError.message;\n\n return {\n errorMessage: `${details[\"errorCode\"] ?? restError.name} (${\n restError.code ?? restError.statusCode\n }): ${message}`,\n errorStatus: restError.statusCode,\n errorType: \"AzureRest\",\n };\n }\n\n // oxlint-disable-next-line no-useless-return\n return;\n};\n\nfunction createAzureContextLogger(context: InvocationContext): LoggerAdapter {\n return {\n debug: context.debug.bind(context),\n error: context.error.bind(context),\n log: context.log.bind(context),\n metadata: { name: \"Azure Functions\" },\n };\n}\n\n/**\n * Utils (@refer https://github.com/Marplex/hono-azurefunc-adapter/)\n */\n\n/** */\nfunction newRequestFromAzureFunctions(request: HttpRequest): Request {\n const hasBody = ![\"GET\", \"HEAD\"].includes(request.method);\n\n return new Request(request.url, {\n headers: headersToObject(request.headers),\n method: request.method,\n ...(hasBody ? { body: request.body as ReadableStream, duplex: \"half\" } : {}),\n });\n}\n\nfunction newAzureFunctionsResponse(response: Response): HttpResponseInit {\n let headers = headersToObject(response.headers);\n let cookies = cookiesFromHeaders(response.headers);\n\n return {\n body: streamToAsyncIterator(response.body),\n cookies,\n headers,\n status: response.status,\n };\n}\n\nfunction headersToObject(input: HttpRequest[\"headers\"]): Record<string, string> {\n const headers: Record<string, string> = {};\n // oxlint-disable-next-line no-array-for-each\n input.forEach((value, key) => (headers[key] = value));\n return headers;\n}\n\nfunction cookiesFromHeaders(headers: Headers): Cookie[] | undefined {\n const cookies = headers.getSetCookie();\n if (cookies.length === 0) {\n return undefined;\n }\n\n return cookies.map((cookie) => parseCookieString(cookie));\n}\n\nfunction parseCookieString(cookieString: string): Cookie {\n const [first, ...attributesArray] = cookieString\n .split(\";\")\n .map((item) => item.split(\"=\"))\n .map(([key, value]) => [key?.trim().toLowerCase(), value ?? \"true\"]);\n\n const [name, encodedValue] = first ?? [];\n const attrs: Record<string, string> = Object.fromEntries(attributesArray);\n\n return {\n domain: attrs[\"domain\"],\n expires: attrs[\"expires\"] ? new Date(attrs[\"expires\"]) : undefined,\n httpOnly: attrs[\"httponly\"] === \"true\",\n maxAge: attrs[\"max-age\"] ? Number.parseInt(attrs[\"max-age\"], 10) : undefined,\n name: name ?? \"\",\n path: attrs[\"path\"],\n sameSite: attrs[\"samesite\"] as \"Strict\" | \"Lax\" | \"None\" | undefined,\n secure: attrs[\"secure\"] === \"true\",\n value: encodedValue ? decodeURIComponent(encodedValue) : \"\",\n };\n}\n\nfunction streamToAsyncIterator(\n readable: Response[\"body\"],\n): AsyncIterableIterator<Uint8Array> | null {\n if (readable === null || !readable) {\n return null;\n }\n\n const reader = readable.getReader();\n return {\n async next() {\n return await reader.read();\n },\n return() {\n reader.releaseLock();\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n } as AsyncIterableIterator<Uint8Array>;\n}\n"],"mappings":"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"}
package/dist/fs.d.mts ADDED
@@ -0,0 +1,37 @@
1
+ import { DatabaseAdapter } from "./~internal/adapter/database.mjs";
2
+ import { StorageAdapter } from "./~internal/adapter/storage.mjs";
3
+ import "./~internal/adapter.mjs";
4
+
5
+ //#region src/adapters/fs.d.ts
6
+
7
+ /**
8
+ * Database adapter for StoryBooker while uses a file (json) in
9
+ * the local filesystem to read from and write entries to.
10
+ * It uses NodeJS FS API to read/write to filesystem.
11
+ *
12
+ * It is useful for testing and playground
13
+ * but not recommended for heavy traffic.
14
+ *
15
+ * Usage:
16
+ * ```ts
17
+ * const database = createLocalFileDatabaseAdapter("./db.json");
18
+ * ```
19
+ */
20
+ declare function createLocalFileDatabaseAdapter(filename?: string): DatabaseAdapter;
21
+ /**
22
+ * Storage adapter for StoryBooker while uses
23
+ * the local filesystem to read from and write files to.
24
+ * It uses NodeJS FS API to read/write to filesystem.
25
+ *
26
+ * It is useful for testing and playground
27
+ * but not recommended for heavy traffic.
28
+ *
29
+ * Usage:
30
+ * ```ts
31
+ * const storage = createLocalFileStorageAdapter("./store/");
32
+ * ```
33
+ */
34
+ declare function createLocalFileStorageAdapter(pathPrefix?: string): StorageAdapter;
35
+ //#endregion
36
+ export { createLocalFileDatabaseAdapter, createLocalFileStorageAdapter };
37
+ //# sourceMappingURL=fs.d.mts.map
package/dist/fs.mjs ADDED
@@ -0,0 +1,240 @@
1
+ import { DatabaseAdapterErrors } from "./~internal/adapter/database.mjs";
2
+ import { StorageAdapterErrors } from "./~internal/adapter/storage.mjs";
3
+ import * as fs$1 from "node:fs";
4
+ import * as fsp$1 from "node:fs/promises";
5
+ import * as path$1 from "node:path";
6
+ import { Readable } from "node:stream";
7
+
8
+ //#region src/adapters/fs.ts
9
+ /**
10
+ * Database adapter for StoryBooker while uses a file (json) in
11
+ * the local filesystem to read from and write entries to.
12
+ * It uses NodeJS FS API to read/write to filesystem.
13
+ *
14
+ * It is useful for testing and playground
15
+ * but not recommended for heavy traffic.
16
+ *
17
+ * Usage:
18
+ * ```ts
19
+ * const database = createLocalFileDatabaseAdapter("./db.json");
20
+ * ```
21
+ */
22
+ function createLocalFileDatabaseAdapter(filename = "db.json") {
23
+ const filepath = path$1.resolve(filename);
24
+ let db = void 0;
25
+ const readFromFile = async (options) => {
26
+ try {
27
+ const newDB = await fsp$1.readFile(filepath, {
28
+ encoding: "utf8",
29
+ signal: options.abortSignal
30
+ });
31
+ db = newDB ? JSON.parse(newDB) : {};
32
+ } catch {
33
+ db = {};
34
+ }
35
+ };
36
+ const saveToFile = async (options) => {
37
+ if (!db) throw new DatabaseAdapterErrors.DatabaseNotInitializedError();
38
+ await fsp$1.writeFile(filepath, JSON.stringify(db, null, 2), {
39
+ encoding: "utf8",
40
+ signal: options.abortSignal
41
+ });
42
+ };
43
+ return {
44
+ metadata: {
45
+ name: "Local File",
46
+ description: "A file based database stored in a single JSON file."
47
+ },
48
+ async init(options) {
49
+ if (fs$1.existsSync(filepath)) if ((await fsp$1.stat(filepath)).isFile()) await readFromFile(options);
50
+ else throw new DatabaseAdapterErrors.DatabaseNotInitializedError(`Path "${filepath}" is not a file`);
51
+ else {
52
+ db = {};
53
+ const basedir = path$1.dirname(filepath);
54
+ await fsp$1.mkdir(basedir, { recursive: true }).catch(() => {});
55
+ await saveToFile(options);
56
+ }
57
+ },
58
+ async createCollection(collectionId, options) {
59
+ if (!db) throw new DatabaseAdapterErrors.DatabaseNotInitializedError();
60
+ if (Object.hasOwn(db, collectionId)) throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId);
61
+ db[collectionId] ??= {};
62
+ await saveToFile(options);
63
+ },
64
+ async listCollections() {
65
+ if (!db) throw new DatabaseAdapterErrors.DatabaseNotInitializedError();
66
+ return Object.keys(db);
67
+ },
68
+ async deleteCollection(collectionId, options) {
69
+ if (!db) throw new DatabaseAdapterErrors.DatabaseNotInitializedError();
70
+ if (!Object.hasOwn(db, collectionId)) throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);
71
+ delete db[collectionId];
72
+ await saveToFile(options);
73
+ },
74
+ async hasCollection(collectionId, _options) {
75
+ if (!db) throw new DatabaseAdapterErrors.DatabaseNotInitializedError();
76
+ return Object.hasOwn(db, collectionId);
77
+ },
78
+ async listDocuments(collectionId, listOptions, _options) {
79
+ if (!db) throw new DatabaseAdapterErrors.DatabaseNotInitializedError();
80
+ if (!Object.hasOwn(db, collectionId)) throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);
81
+ const { limit = Number.POSITIVE_INFINITY, sort, filter } = listOptions || {};
82
+ const collection = db[collectionId];
83
+ const items = Object.values(collection);
84
+ if (sort) {
85
+ if (typeof sort === "function") items.sort(sort);
86
+ else if (sort === "latest") items.sort((itemA, itemB) => {
87
+ return new Date(itemB.updatedAt).getTime() - new Date(itemA.updatedAt).getTime();
88
+ });
89
+ }
90
+ if (filter && typeof filter === "function") return items.filter((item) => filter(item)).slice(0, limit);
91
+ return items.slice(0, limit);
92
+ },
93
+ async getDocument(collectionId, documentId, _options) {
94
+ if (!db) throw new DatabaseAdapterErrors.DatabaseNotInitializedError();
95
+ if (!Object.hasOwn(db, collectionId)) throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);
96
+ const item = db[collectionId]?.[documentId];
97
+ if (!item) throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);
98
+ return item;
99
+ },
100
+ async hasDocument(collectionId, documentId, options) {
101
+ return Boolean(await this.getDocument(collectionId, documentId, options));
102
+ },
103
+ async createDocument(collectionId, documentData, options) {
104
+ if (!db) throw new DatabaseAdapterErrors.DatabaseNotInitializedError();
105
+ if (!Object.hasOwn(db, collectionId)) throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);
106
+ const collection = db[collectionId];
107
+ if (collection[documentData.id]) throw new DatabaseAdapterErrors.DocumentAlreadyExistsError(collectionId, documentData.id);
108
+ collection[documentData.id] = documentData;
109
+ await saveToFile(options);
110
+ },
111
+ async deleteDocument(collectionId, documentId, options) {
112
+ if (!db) throw new DatabaseAdapterErrors.DatabaseNotInitializedError();
113
+ if (!Object.hasOwn(db, collectionId)) throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);
114
+ if (!await this.hasDocument(collectionId, documentId, options)) throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);
115
+ const collection = db[collectionId];
116
+ delete collection[documentId];
117
+ await saveToFile(options);
118
+ },
119
+ async updateDocument(collectionId, documentId, documentData, options) {
120
+ if (!db) throw new DatabaseAdapterErrors.DatabaseNotInitializedError();
121
+ if (!Object.hasOwn(db, collectionId)) throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);
122
+ const prevItem = await this.getDocument(collectionId, documentId, options);
123
+ if (!prevItem) throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);
124
+ const collection = db[collectionId];
125
+ collection[documentId] = {
126
+ ...prevItem,
127
+ ...documentData,
128
+ id: documentId
129
+ };
130
+ await saveToFile(options);
131
+ }
132
+ };
133
+ }
134
+ /**
135
+ * Storage adapter for StoryBooker while uses
136
+ * the local filesystem to read from and write files to.
137
+ * It uses NodeJS FS API to read/write to filesystem.
138
+ *
139
+ * It is useful for testing and playground
140
+ * but not recommended for heavy traffic.
141
+ *
142
+ * Usage:
143
+ * ```ts
144
+ * const storage = createLocalFileStorageAdapter("./store/");
145
+ * ```
146
+ */
147
+ function createLocalFileStorageAdapter(pathPrefix = ".") {
148
+ const basePath = path$1.resolve(pathPrefix);
149
+ function genPath(...pathParts) {
150
+ return path$1.join(basePath, ...pathParts.filter((part) => part !== void 0));
151
+ }
152
+ return {
153
+ metadata: {
154
+ name: "Local File System",
155
+ description: "A storage adapter that uses the local file system to store files."
156
+ },
157
+ init: async (_options) => {
158
+ try {
159
+ await fsp$1.mkdir(basePath, { recursive: true });
160
+ } catch (error) {
161
+ throw new StorageAdapterErrors.StorageNotInitializedError({ cause: error });
162
+ }
163
+ },
164
+ async createContainer(containerId, options) {
165
+ if (await this.hasContainer(containerId, options)) throw new StorageAdapterErrors.ContainerAlreadyExistsError(containerId);
166
+ await fsp$1.mkdir(genPath(containerId), { recursive: true });
167
+ },
168
+ async deleteContainer(containerId, options) {
169
+ if (!await this.hasContainer(containerId, options)) throw new StorageAdapterErrors.ContainerDoesNotExistError(containerId);
170
+ await fsp$1.rm(genPath(containerId), {
171
+ force: true,
172
+ recursive: true
173
+ });
174
+ },
175
+ async hasContainer(containerId) {
176
+ return fs$1.existsSync(genPath(containerId));
177
+ },
178
+ async listContainers() {
179
+ const dirPath = genPath();
180
+ if (!fs$1.existsSync(dirPath)) throw new StorageAdapterErrors.StorageNotInitializedError(`Dir "${dirPath}" does not exist`);
181
+ const containers = [];
182
+ const entries = await fsp$1.readdir(dirPath, { withFileTypes: true });
183
+ for (const entry of entries) if (entry.isDirectory()) containers.push(entry.name);
184
+ return containers;
185
+ },
186
+ async deleteFiles(containerId, filePathsOrPrefix) {
187
+ if (typeof filePathsOrPrefix === "string") await fsp$1.rm(genPath(containerId, filePathsOrPrefix), {
188
+ force: true,
189
+ recursive: true
190
+ });
191
+ else for (const filepath of filePathsOrPrefix) await fsp$1.rm(filepath, {
192
+ force: true,
193
+ recursive: true
194
+ });
195
+ },
196
+ async hasFile(containerId, filepath) {
197
+ const path$2 = genPath(containerId, filepath);
198
+ return fs$1.existsSync(path$2);
199
+ },
200
+ async downloadFile(containerId, filepath, options) {
201
+ if (!await this.hasFile(containerId, filepath, options)) throw new StorageAdapterErrors.FileDoesNotExistError(containerId, filepath);
202
+ const path$2 = genPath(containerId, filepath);
203
+ const buffer = await fsp$1.readFile(path$2);
204
+ return {
205
+ content: new Blob([buffer]),
206
+ path: path$2
207
+ };
208
+ },
209
+ async uploadFiles(containerId, files, options) {
210
+ for (const file of files) {
211
+ const filepath = genPath(containerId, file.path);
212
+ const dirpath = path$1.dirname(filepath);
213
+ await fsp$1.mkdir(dirpath, { recursive: true });
214
+ if (file.content instanceof ReadableStream) await writeWebStreamToFile(file.content, filepath);
215
+ else {
216
+ const data = typeof file.content === "string" ? file.content : await file.content.text();
217
+ await fsp$1.writeFile(filepath, data, {
218
+ encoding: "utf8",
219
+ signal: options.abortSignal
220
+ });
221
+ }
222
+ }
223
+ }
224
+ };
225
+ }
226
+ function writeWebStreamToFile(webReadableStream, outputPath) {
227
+ const nodeReadableStream = Readable.fromWeb(webReadableStream);
228
+ const fileWritableStream = fs$1.createWriteStream(outputPath);
229
+ nodeReadableStream.pipe(fileWritableStream);
230
+ return new Promise((resolve, reject) => {
231
+ fileWritableStream.on("finish", () => {
232
+ resolve(null);
233
+ });
234
+ fileWritableStream.on("error", reject);
235
+ });
236
+ }
237
+
238
+ //#endregion
239
+ export { createLocalFileDatabaseAdapter, createLocalFileStorageAdapter };
240
+ //# sourceMappingURL=fs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.mjs","names":["path","db: Record<string, Record<string, StoryBookerDatabaseDocument>> | undefined","fsp","fs","containers: string[]","data: string | Stream"],"sources":["../src/adapters/fs.ts"],"sourcesContent":["// oxlint-disable no-await-in-loop\n// oxlint-disable max-params\n// oxlint-disable require-await\n// oxlint-disable no-unsafe-assignment\n\nimport type { Buffer } from \"node:buffer\";\nimport * as fs from \"node:fs\";\nimport * as fsp from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { Readable, type Stream } from \"node:stream\";\nimport type { ReadableStream as WebReadableStream } from \"node:stream/web\";\nimport {\n DatabaseAdapterErrors,\n StorageAdapterErrors,\n type DatabaseAdapter,\n type DatabaseAdapterOptions,\n type StorageAdapter,\n type StoryBookerDatabaseDocument,\n} from \"./_internal/index.ts\";\n\n/**\n * Database adapter for StoryBooker while uses a file (json) in\n * the local filesystem to read from and write entries to.\n * It uses NodeJS FS API to read/write to filesystem.\n *\n * It is useful for testing and playground\n * but not recommended for heavy traffic.\n *\n * Usage:\n * ```ts\n * const database = createLocalFileDatabaseAdapter(\"./db.json\");\n * ```\n */\nexport function createLocalFileDatabaseAdapter(filename = \"db.json\"): DatabaseAdapter {\n const filepath = path.resolve(filename);\n let db: Record<string, Record<string, StoryBookerDatabaseDocument>> | undefined = undefined;\n\n const readFromFile = async (options: DatabaseAdapterOptions): Promise<void> => {\n try {\n const newDB = await fsp.readFile(filepath, {\n encoding: \"utf8\",\n signal: options.abortSignal,\n });\n db = newDB ? JSON.parse(newDB) : {};\n } catch {\n db = {};\n }\n };\n\n const saveToFile = async (options: DatabaseAdapterOptions): Promise<void> => {\n if (!db) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError();\n }\n\n await fsp.writeFile(filepath, JSON.stringify(db, null, 2), {\n encoding: \"utf8\",\n signal: options.abortSignal,\n });\n };\n\n return {\n metadata: {\n name: \"Local File\",\n description: \"A file based database stored in a single JSON file.\",\n },\n\n async init(options) {\n if (fs.existsSync(filepath)) {\n const stat = await fsp.stat(filepath);\n if (stat.isFile()) {\n await readFromFile(options);\n } else {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError(\n `Path \"${filepath}\" is not a file`,\n );\n }\n } else {\n db = {}; // Initialize empty DB\n const basedir = path.dirname(filepath);\n await fsp.mkdir(basedir, { recursive: true }).catch(() => {\n // ignore error\n });\n await saveToFile(options);\n }\n },\n\n // Collections\n\n async createCollection(collectionId, options) {\n if (!db) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError();\n }\n\n if (Object.hasOwn(db, collectionId)) {\n throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId);\n }\n\n db[collectionId] ??= {};\n await saveToFile(options);\n },\n\n async listCollections() {\n if (!db) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError();\n }\n\n return Object.keys(db);\n },\n\n async deleteCollection(collectionId, options) {\n if (!db) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError();\n }\n\n if (!Object.hasOwn(db, collectionId)) {\n throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);\n }\n\n // oxlint-disable-next-line no-dynamic-delete\n delete db[collectionId];\n await saveToFile(options);\n },\n\n async hasCollection(collectionId, _options) {\n if (!db) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError();\n }\n\n return Object.hasOwn(db, collectionId);\n },\n\n // Documents\n\n async listDocuments(collectionId, listOptions, _options) {\n if (!db) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError();\n }\n\n if (!Object.hasOwn(db, collectionId)) {\n throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);\n }\n\n const { limit = Number.POSITIVE_INFINITY, sort, filter } = listOptions || {};\n\n // oxlint-disable-next-line no-non-null-assertion\n const collection = db[collectionId]!;\n const items = Object.values(collection);\n if (sort) {\n if (typeof sort === \"function\") {\n items.sort(sort);\n } else if (sort === \"latest\") {\n items.sort((itemA, itemB) => {\n return new Date(itemB.updatedAt).getTime() - new Date(itemA.updatedAt).getTime();\n });\n }\n }\n\n if (filter && typeof filter === \"function\") {\n return items.filter((item) => filter(item)).slice(0, limit);\n }\n\n return items.slice(0, limit);\n },\n\n async getDocument(collectionId, documentId, _options) {\n if (!db) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError();\n }\n\n if (!Object.hasOwn(db, collectionId)) {\n throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);\n }\n\n const item = db[collectionId]?.[documentId];\n if (!item) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);\n }\n\n return item;\n },\n\n async hasDocument(collectionId, documentId, options) {\n return Boolean(await this.getDocument(collectionId, documentId, options));\n },\n\n async createDocument(collectionId, documentData, options) {\n if (!db) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError();\n }\n\n if (!Object.hasOwn(db, collectionId)) {\n throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);\n }\n\n // oxlint-disable-next-line no-non-null-assertion\n const collection = db[collectionId]!;\n if (collection[documentData.id]) {\n throw new DatabaseAdapterErrors.DocumentAlreadyExistsError(collectionId, documentData.id);\n }\n\n collection[documentData.id] = documentData;\n await saveToFile(options);\n },\n\n async deleteDocument(collectionId, documentId, options) {\n if (!db) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError();\n }\n\n if (!Object.hasOwn(db, collectionId)) {\n throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);\n }\n\n if (!(await this.hasDocument(collectionId, documentId, options))) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);\n }\n\n // oxlint-disable-next-line no-non-null-assertion\n const collection = db[collectionId]!;\n // oxlint-disable-next-line no-dynamic-delete\n delete collection[documentId];\n await saveToFile(options);\n },\n\n async updateDocument(collectionId, documentId, documentData, options) {\n if (!db) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError();\n }\n\n if (!Object.hasOwn(db, collectionId)) {\n throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId);\n }\n\n const prevItem = await this.getDocument(collectionId, documentId, options);\n if (!prevItem) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);\n }\n\n // oxlint-disable-next-line no-non-null-assertion\n const collection = db[collectionId]!;\n collection[documentId] = { ...prevItem, ...documentData, id: documentId };\n await saveToFile(options);\n },\n };\n}\n\n/**\n * Storage adapter for StoryBooker while uses\n * the local filesystem to read from and write files to.\n * It uses NodeJS FS API to read/write to filesystem.\n *\n * It is useful for testing and playground\n * but not recommended for heavy traffic.\n *\n * Usage:\n * ```ts\n * const storage = createLocalFileStorageAdapter(\"./store/\");\n * ```\n */\nexport function createLocalFileStorageAdapter(pathPrefix = \".\"): StorageAdapter {\n const basePath = path.resolve(pathPrefix);\n\n function genPath(...pathParts: (string | undefined)[]): string {\n return path.join(basePath, ...pathParts.filter((part) => part !== undefined));\n }\n\n // Containers\n\n return {\n metadata: {\n name: \"Local File System\",\n description: \"A storage adapter that uses the local file system to store files.\",\n },\n\n init: async (_options) => {\n try {\n await fsp.mkdir(basePath, { recursive: true });\n } catch (error) {\n throw new StorageAdapterErrors.StorageNotInitializedError({ cause: error });\n }\n },\n\n async createContainer(containerId, options) {\n if (await this.hasContainer(containerId, options)) {\n throw new StorageAdapterErrors.ContainerAlreadyExistsError(containerId);\n }\n\n await fsp.mkdir(genPath(containerId), { recursive: true });\n },\n\n async deleteContainer(containerId, options) {\n if (!(await this.hasContainer(containerId, options))) {\n throw new StorageAdapterErrors.ContainerDoesNotExistError(containerId);\n }\n\n await fsp.rm(genPath(containerId), { force: true, recursive: true });\n },\n\n async hasContainer(containerId) {\n return fs.existsSync(genPath(containerId));\n },\n\n async listContainers() {\n const dirPath = genPath();\n if (!fs.existsSync(dirPath)) {\n throw new StorageAdapterErrors.StorageNotInitializedError(\n `Dir \"${dirPath}\" does not exist`,\n );\n }\n\n const containers: string[] = [];\n const entries = await fsp.readdir(dirPath, {\n withFileTypes: true,\n });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n containers.push(entry.name);\n }\n }\n return containers;\n },\n\n // Files\n\n async deleteFiles(containerId, filePathsOrPrefix) {\n if (typeof filePathsOrPrefix === \"string\") {\n await fsp.rm(genPath(containerId, filePathsOrPrefix), {\n force: true,\n recursive: true,\n });\n } else {\n for (const filepath of filePathsOrPrefix) {\n // oxlint-disable-next-line no-await-in-loop\n await fsp.rm(filepath, { force: true, recursive: true });\n }\n }\n },\n\n async hasFile(containerId, filepath) {\n const path = genPath(containerId, filepath);\n return fs.existsSync(path);\n },\n\n async downloadFile(containerId, filepath, options) {\n if (!(await this.hasFile(containerId, filepath, options))) {\n throw new StorageAdapterErrors.FileDoesNotExistError(containerId, filepath);\n }\n\n const path = genPath(containerId, filepath);\n const buffer = await fsp.readFile(path);\n const content = new Blob([buffer as Buffer<ArrayBuffer>]);\n return { content, path };\n },\n\n async uploadFiles(containerId, files, options) {\n for (const file of files) {\n const filepath = genPath(containerId, file.path);\n const dirpath = path.dirname(filepath);\n\n await fsp.mkdir(dirpath, { recursive: true });\n if (file.content instanceof ReadableStream) {\n await writeWebStreamToFile(file.content, filepath);\n } else {\n const data: string | Stream =\n // oxlint-disable-next-line no-nested-ternary\n typeof file.content === \"string\" ? file.content : await file.content.text();\n\n await fsp.writeFile(filepath, data, {\n encoding: \"utf8\",\n signal: options.abortSignal,\n });\n }\n }\n },\n };\n}\n\nfunction writeWebStreamToFile(\n webReadableStream: ReadableStream,\n outputPath: string,\n): Promise<null> {\n // Convert WebReadableStream to Node.js Readable stream\n const nodeReadableStream = Readable.fromWeb(webReadableStream as WebReadableStream);\n\n // Create a writable file stream\n const fileWritableStream = fs.createWriteStream(outputPath);\n\n // Pipe the Node.js readable stream to the writable file stream\n nodeReadableStream.pipe(fileWritableStream);\n\n // Return a promise that resolves when writing is finished\n return new Promise((resolve, reject) => {\n fileWritableStream.on(\"finish\", () => {\n resolve(null);\n });\n fileWritableStream.on(\"error\", reject);\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { DatabaseAdapter } from "./~internal/adapter/database.mjs";
2
+ import { Bigtable } from "@google-cloud/bigtable";
3
+
4
+ //#region src/adapters/gcp-big-table.d.ts
5
+ declare class GcpBigtableDatabaseAdapter implements DatabaseAdapter {
6
+ #private;
7
+ constructor(client: Bigtable, instanceName?: string);
8
+ metadata: DatabaseAdapter["metadata"];
9
+ init: DatabaseAdapter["init"];
10
+ listCollections: DatabaseAdapter["listCollections"];
11
+ createCollection: DatabaseAdapter["createCollection"];
12
+ hasCollection: DatabaseAdapter["hasCollection"];
13
+ deleteCollection: DatabaseAdapter["deleteCollection"];
14
+ listDocuments: DatabaseAdapter["listDocuments"];
15
+ getDocument: DatabaseAdapter["getDocument"];
16
+ createDocument: DatabaseAdapter["createDocument"];
17
+ hasDocument: DatabaseAdapter["hasDocument"];
18
+ deleteDocument: DatabaseAdapter["deleteDocument"];
19
+ updateDocument: DatabaseAdapter["updateDocument"];
20
+ }
21
+ //#endregion
22
+ export { GcpBigtableDatabaseAdapter };
23
+ //# sourceMappingURL=gcp-big-table.d.mts.map
@@ -0,0 +1,92 @@
1
+ import { DatabaseAdapterErrors } from "./~internal/adapter/database.mjs";
2
+
3
+ //#region src/adapters/gcp-big-table.ts
4
+ const COLUMN_FAMILY = "cf1";
5
+ var GcpBigtableDatabaseAdapter = class {
6
+ #instance;
7
+ constructor(client, instanceName = "StoryBooker") {
8
+ this.#instance = client.instance(instanceName);
9
+ }
10
+ metadata = { name: "Google Cloud Bigtable" };
11
+ init = async (_options) => {
12
+ const [exists] = await this.#instance.exists();
13
+ if (!exists) throw new DatabaseAdapterErrors.DatabaseNotInitializedError(`Bigtable instance '${this.#instance.id}' does not exist.`);
14
+ };
15
+ listCollections = async (_options) => {
16
+ const [tables] = await this.#instance.getTables();
17
+ return tables.map((table) => table.id);
18
+ };
19
+ createCollection = async (collectionId, _options) => {
20
+ try {
21
+ await this.#instance.createTable(collectionId, { families: [COLUMN_FAMILY] });
22
+ } catch (error) {
23
+ throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId, error);
24
+ }
25
+ };
26
+ hasCollection = async (collectionId, _options) => {
27
+ const [exists] = await this.#instance.table(collectionId).exists();
28
+ return exists;
29
+ };
30
+ deleteCollection = async (collectionId, _options) => {
31
+ try {
32
+ await this.#instance.table(collectionId).delete();
33
+ } catch (error) {
34
+ throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);
35
+ }
36
+ };
37
+ listDocuments = async (collectionId, _listOptions, _options) => {
38
+ try {
39
+ const [rows] = await this.#instance.table(collectionId).getRows();
40
+ const list = [];
41
+ for (const row of rows) {
42
+ const document = {
43
+ ...row.data[COLUMN_FAMILY],
44
+ id: row.id
45
+ };
46
+ list.push(document);
47
+ }
48
+ return list;
49
+ } catch (error) {
50
+ throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);
51
+ }
52
+ };
53
+ getDocument = async (collectionId, documentId, _options) => {
54
+ const row = this.#instance.table(collectionId).row(documentId);
55
+ const [exists] = await row.exists();
56
+ if (!exists) throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);
57
+ const [rowData] = await row.get([COLUMN_FAMILY]);
58
+ return {
59
+ ...rowData,
60
+ id: documentId
61
+ };
62
+ };
63
+ createDocument = async (collectionId, documentData, _options) => {
64
+ try {
65
+ await this.#instance.table(collectionId).row(documentData.id).create({ entry: { [COLUMN_FAMILY]: documentData } });
66
+ } catch (error) {
67
+ throw new DatabaseAdapterErrors.DocumentAlreadyExistsError(collectionId, documentData.id, error);
68
+ }
69
+ };
70
+ hasDocument = async (collectionId, documentId, _options) => {
71
+ const [exists] = await this.#instance.table(collectionId).row(documentId).exists();
72
+ return exists;
73
+ };
74
+ deleteDocument = async (collectionId, documentId, _options) => {
75
+ try {
76
+ await this.#instance.table(collectionId).row(documentId).delete();
77
+ } catch (error) {
78
+ throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
79
+ }
80
+ };
81
+ updateDocument = async (collectionId, documentId, documentData) => {
82
+ try {
83
+ await this.#instance.table(collectionId).row(documentId).save({ [COLUMN_FAMILY]: documentData });
84
+ } catch (error) {
85
+ throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
86
+ }
87
+ };
88
+ };
89
+
90
+ //#endregion
91
+ export { GcpBigtableDatabaseAdapter };
92
+ //# sourceMappingURL=gcp-big-table.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gcp-big-table.mjs","names":["COLUMN_FAMILY: ColumnFamily","#instance","list: Document[]","document: Document"],"sources":["../src/adapters/gcp-big-table.ts"],"sourcesContent":["import type { Bigtable, Instance } from \"@google-cloud/bigtable\";\nimport {\n DatabaseAdapterErrors,\n type DatabaseAdapter,\n type DatabaseAdapterOptions,\n type DatabaseDocumentListOptions,\n type StoryBookerDatabaseDocument,\n} from \"./_internal/database.ts\";\n\ntype ColumnFamily = \"cf1\";\nconst COLUMN_FAMILY: ColumnFamily = \"cf1\";\n\nexport class GcpBigtableDatabaseAdapter implements DatabaseAdapter {\n #instance: Instance;\n\n constructor(client: Bigtable, instanceName = \"StoryBooker\") {\n this.#instance = client.instance(instanceName);\n }\n\n metadata: DatabaseAdapter[\"metadata\"] = { name: \"Google Cloud Bigtable\" };\n\n init: DatabaseAdapter[\"init\"] = async (_options) => {\n // Bigtable instances are typically created outside of app code (via console/IaC)\n // Optionally, check if instance exists\n const [exists] = await this.#instance.exists();\n if (!exists) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError(\n `Bigtable instance '${this.#instance.id}' does not exist.`,\n );\n }\n };\n\n listCollections: DatabaseAdapter[\"listCollections\"] = async (_options) => {\n const [tables] = await this.#instance.getTables();\n return tables.map((table) => table.id);\n };\n\n createCollection: DatabaseAdapter[\"createCollection\"] = async (collectionId, _options) => {\n try {\n await this.#instance.createTable(collectionId, {\n families: [COLUMN_FAMILY],\n });\n } catch (error) {\n throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId, error);\n }\n };\n\n hasCollection: DatabaseAdapter[\"hasCollection\"] = async (collectionId, _options) => {\n const table = this.#instance.table(collectionId);\n const [exists] = await table.exists();\n return exists;\n };\n\n deleteCollection: DatabaseAdapter[\"deleteCollection\"] = async (collectionId, _options) => {\n try {\n const table = this.#instance.table(collectionId);\n await table.delete();\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 try {\n const table = this.#instance.table(collectionId);\n const [rows] = await table.getRows();\n const list: Document[] = [];\n for (const row of rows) {\n const data = (row.data as Record<ColumnFamily, Document>)[COLUMN_FAMILY];\n const document: Document = { ...data, id: row.id };\n list.push(document);\n }\n\n return list;\n } catch (error) {\n throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);\n }\n };\n\n getDocument: DatabaseAdapter[\"getDocument\"] = async <\n Document extends StoryBookerDatabaseDocument,\n >(\n collectionId: string,\n documentId: string,\n _options: DatabaseAdapterOptions,\n ) => {\n const table = this.#instance.table(collectionId);\n const row = table.row(documentId);\n const [exists] = await row.exists();\n if (!exists) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);\n }\n\n const [rowData] = await row.get<Document>([COLUMN_FAMILY]);\n\n return { ...rowData, id: documentId };\n };\n\n createDocument: DatabaseAdapter[\"createDocument\"] = async (\n collectionId,\n documentData,\n _options,\n ) => {\n try {\n const table = this.#instance.table(collectionId);\n const row = table.row(documentData.id);\n await row.create({ entry: { [COLUMN_FAMILY]: documentData } });\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 table = this.#instance.table(collectionId);\n const row = table.row(documentId);\n const [exists] = await row.exists();\n return exists;\n };\n\n deleteDocument: DatabaseAdapter[\"deleteDocument\"] = async (\n collectionId,\n documentId,\n _options,\n ) => {\n try {\n const table = this.#instance.table(collectionId);\n const row = table.row(documentId);\n await row.delete();\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n\n updateDocument: DatabaseAdapter[\"updateDocument\"] = async (\n collectionId,\n documentId,\n documentData,\n ) => {\n try {\n const table = this.#instance.table(collectionId);\n const row = table.row(documentId);\n await row.save({ [COLUMN_FAMILY]: documentData });\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n}\n"],"mappings":";;;AAUA,MAAMA,gBAA8B;AAEpC,IAAa,6BAAb,MAAmE;CACjE;CAEA,YAAY,QAAkB,eAAe,eAAe;AAC1D,QAAKC,WAAY,OAAO,SAAS,aAAa;;CAGhD,WAAwC,EAAE,MAAM,yBAAyB;CAEzE,OAAgC,OAAO,aAAa;EAGlD,MAAM,CAAC,UAAU,MAAM,MAAKA,SAAU,QAAQ;AAC9C,MAAI,CAAC,OACH,OAAM,IAAI,sBAAsB,4BAC9B,sBAAsB,MAAKA,SAAU,GAAG,mBACzC;;CAIL,kBAAsD,OAAO,aAAa;EACxE,MAAM,CAAC,UAAU,MAAM,MAAKA,SAAU,WAAW;AACjD,SAAO,OAAO,KAAK,UAAU,MAAM,GAAG;;CAGxC,mBAAwD,OAAO,cAAc,aAAa;AACxF,MAAI;AACF,SAAM,MAAKA,SAAU,YAAY,cAAc,EAC7C,UAAU,CAAC,cAAc,EAC1B,CAAC;WACK,OAAO;AACd,SAAM,IAAI,sBAAsB,6BAA6B,cAAc,MAAM;;;CAIrF,gBAAkD,OAAO,cAAc,aAAa;EAElF,MAAM,CAAC,UAAU,MADH,MAAKA,SAAU,MAAM,aAAa,CACnB,QAAQ;AACrC,SAAO;;CAGT,mBAAwD,OAAO,cAAc,aAAa;AACxF,MAAI;AAEF,SADc,MAAKA,SAAU,MAAM,aAAa,CACpC,QAAQ;WACb,OAAO;AACd,SAAM,IAAI,sBAAsB,4BAA4B,cAAc,MAAM;;;CAIpF,gBAAkD,OAGhD,cACA,cACA,aACG;AACH,MAAI;GAEF,MAAM,CAAC,QAAQ,MADD,MAAKA,SAAU,MAAM,aAAa,CACrB,SAAS;GACpC,MAAMC,OAAmB,EAAE;AAC3B,QAAK,MAAM,OAAO,MAAM;IAEtB,MAAMC,WAAqB;KAAE,GADf,IAAI,KAAwC;KACpB,IAAI,IAAI;KAAI;AAClD,SAAK,KAAK,SAAS;;AAGrB,UAAO;WACA,OAAO;AACd,SAAM,IAAI,sBAAsB,4BAA4B,cAAc,MAAM;;;CAIpF,cAA8C,OAG5C,cACA,YACA,aACG;EAEH,MAAM,MADQ,MAAKF,SAAU,MAAM,aAAa,CAC9B,IAAI,WAAW;EACjC,MAAM,CAAC,UAAU,MAAM,IAAI,QAAQ;AACnC,MAAI,CAAC,OACH,OAAM,IAAI,sBAAsB,0BAA0B,cAAc,WAAW;EAGrF,MAAM,CAAC,WAAW,MAAM,IAAI,IAAc,CAAC,cAAc,CAAC;AAE1D,SAAO;GAAE,GAAG;GAAS,IAAI;GAAY;;CAGvC,iBAAoD,OAClD,cACA,cACA,aACG;AACH,MAAI;AAGF,SAFc,MAAKA,SAAU,MAAM,aAAa,CAC9B,IAAI,aAAa,GAAG,CAC5B,OAAO,EAAE,OAAO,GAAG,gBAAgB,cAAc,EAAE,CAAC;WACvD,OAAO;AACd,SAAM,IAAI,sBAAsB,2BAC9B,cACA,aAAa,IACb,MACD;;;CAIL,cAA8C,OAAO,cAAc,YAAY,aAAa;EAG1F,MAAM,CAAC,UAAU,MAFH,MAAKA,SAAU,MAAM,aAAa,CAC9B,IAAI,WAAW,CACN,QAAQ;AACnC,SAAO;;CAGT,iBAAoD,OAClD,cACA,YACA,aACG;AACH,MAAI;AAGF,SAFc,MAAKA,SAAU,MAAM,aAAa,CAC9B,IAAI,WAAW,CACvB,QAAQ;WACX,OAAO;AACd,SAAM,IAAI,sBAAsB,0BAA0B,cAAc,YAAY,MAAM;;;CAI9F,iBAAoD,OAClD,cACA,YACA,iBACG;AACH,MAAI;AAGF,SAFc,MAAKA,SAAU,MAAM,aAAa,CAC9B,IAAI,WAAW,CACvB,KAAK,GAAG,gBAAgB,cAAc,CAAC;WAC1C,OAAO;AACd,SAAM,IAAI,sBAAsB,0BAA0B,cAAc,YAAY,MAAM"}
@@ -0,0 +1,22 @@
1
+ import { DatabaseAdapter } from "./~internal/adapter/database.mjs";
2
+ import { Firestore } from "@google-cloud/firestore";
3
+
4
+ //#region src/adapters/gcp-firestore.d.ts
5
+ declare class GcpFirestoreDatabaseAdapter implements DatabaseAdapter {
6
+ #private;
7
+ constructor(instance: Firestore);
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 { GcpFirestoreDatabaseAdapter };
22
+ //# sourceMappingURL=gcp-firestore.d.mts.map