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.
- package/README.md +40 -18
- package/dist/adapters/_internal/queue.d.mts +127 -0
- package/dist/aws-dynamodb.d.mts +22 -0
- package/dist/aws-dynamodb.mjs +118 -0
- package/dist/aws-dynamodb.mjs.map +1 -0
- package/dist/aws-s3.d.mts +20 -0
- package/dist/aws-s3.mjs +96 -0
- package/dist/aws-s3.mjs.map +1 -0
- package/dist/azure-blob-storage.d.mts +20 -0
- package/dist/azure-blob-storage.mjs +126 -0
- package/dist/azure-blob-storage.mjs.map +1 -0
- package/dist/azure-cosmos-db.d.mts +23 -0
- package/dist/azure-cosmos-db.mjs +87 -0
- package/dist/azure-cosmos-db.mjs.map +1 -0
- package/dist/azure-data-tables.d.mts +23 -0
- package/dist/azure-data-tables.mjs +127 -0
- package/dist/azure-data-tables.mjs.map +1 -0
- package/dist/azure-easy-auth.d.mts +50 -0
- package/dist/azure-easy-auth.mjs +88 -0
- package/dist/azure-easy-auth.mjs.map +1 -0
- package/dist/azure-functions.d.mts +62 -0
- package/dist/azure-functions.mjs +147 -0
- package/dist/azure-functions.mjs.map +1 -0
- package/dist/fs.d.mts +37 -0
- package/dist/fs.mjs +240 -0
- package/dist/fs.mjs.map +1 -0
- package/dist/gcp-big-table.d.mts +23 -0
- package/dist/gcp-big-table.mjs +92 -0
- package/dist/gcp-big-table.mjs.map +1 -0
- package/dist/gcp-firestore.d.mts +22 -0
- package/dist/gcp-firestore.mjs +87 -0
- package/dist/gcp-firestore.mjs.map +1 -0
- package/dist/gcp-storage.d.mts +20 -0
- package/dist/gcp-storage.mjs +96 -0
- package/dist/gcp-storage.mjs.map +1 -0
- package/dist/handlers/handle-process-zip.mjs +90 -0
- package/dist/handlers/handle-process-zip.mjs.map +1 -0
- package/dist/handlers/handle-purge.d.mts +12 -0
- package/dist/handlers/handle-purge.mjs +36 -0
- package/dist/handlers/handle-purge.mjs.map +1 -0
- package/dist/handlers/handle-serve-storybook.mjs +94 -0
- package/dist/handlers/handle-serve-storybook.mjs.map +1 -0
- package/dist/index.d.mts +28 -0
- package/dist/index.mjs +62 -0
- package/dist/index.mjs.map +1 -0
- package/dist/models/builds-model.mjs +248 -0
- package/dist/models/builds-model.mjs.map +1 -0
- package/dist/models/builds-schema.d.mts +171 -0
- package/dist/models/builds-schema.mjs +67 -0
- package/dist/models/builds-schema.mjs.map +1 -0
- package/dist/models/projects-model.mjs +122 -0
- package/dist/models/projects-model.mjs.map +1 -0
- package/dist/models/projects-schema.d.mts +70 -0
- package/dist/models/projects-schema.mjs +37 -0
- package/dist/models/projects-schema.mjs.map +1 -0
- package/dist/models/tags-model.mjs +110 -0
- package/dist/models/tags-model.mjs.map +1 -0
- package/dist/models/tags-schema.d.mts +76 -0
- package/dist/models/tags-schema.mjs +34 -0
- package/dist/models/tags-schema.mjs.map +1 -0
- package/dist/models/~model.mjs +43 -0
- package/dist/models/~model.mjs.map +1 -0
- package/dist/models/~shared-schema.d.mts +1 -0
- package/dist/models/~shared-schema.mjs +20 -0
- package/dist/models/~shared-schema.mjs.map +1 -0
- package/dist/mysql.d.mts +39 -0
- package/dist/mysql.mjs +151 -0
- package/dist/mysql.mjs.map +1 -0
- package/dist/redis.d.mts +33 -0
- package/dist/redis.mjs +118 -0
- package/dist/redis.mjs.map +1 -0
- package/dist/routers/account-router.mjs +91 -0
- package/dist/routers/account-router.mjs.map +1 -0
- package/dist/routers/builds-router.mjs +347 -0
- package/dist/routers/builds-router.mjs.map +1 -0
- package/dist/routers/projects-router.mjs +236 -0
- package/dist/routers/projects-router.mjs.map +1 -0
- package/dist/routers/root-router.mjs +108 -0
- package/dist/routers/root-router.mjs.map +1 -0
- package/dist/routers/tags-router.mjs +269 -0
- package/dist/routers/tags-router.mjs.map +1 -0
- package/dist/routers/tasks-router.mjs +71 -0
- package/dist/routers/tasks-router.mjs.map +1 -0
- package/dist/urls.d.mts +47 -0
- package/dist/urls.mjs +208 -0
- package/dist/urls.mjs.map +1 -0
- package/dist/utils/adapter-utils.d.mts +14 -0
- package/dist/utils/adapter-utils.mjs +14 -0
- package/dist/utils/adapter-utils.mjs.map +1 -0
- package/dist/utils/auth.mjs +25 -0
- package/dist/utils/auth.mjs.map +1 -0
- package/dist/utils/error.d.mts +21 -0
- package/dist/utils/error.mjs +109 -0
- package/dist/utils/error.mjs.map +1 -0
- package/dist/utils/file-utils.mjs +16 -0
- package/dist/utils/file-utils.mjs.map +1 -0
- package/dist/utils/openapi-utils.mjs +45 -0
- package/dist/utils/openapi-utils.mjs.map +1 -0
- package/dist/utils/request.mjs +35 -0
- package/dist/utils/request.mjs.map +1 -0
- package/dist/utils/response.mjs +24 -0
- package/dist/utils/response.mjs.map +1 -0
- package/dist/utils/store.mjs +54 -0
- package/dist/utils/store.mjs.map +1 -0
- package/dist/utils/ui-utils.mjs +38 -0
- package/dist/utils/ui-utils.mjs.map +1 -0
- package/dist/utils/url-utils.d.mts +10 -0
- package/dist/utils/url-utils.mjs +54 -0
- package/dist/utils/url-utils.mjs.map +1 -0
- package/dist/~internal/adapter/auth.d.mts +123 -0
- package/dist/~internal/adapter/auth.mjs +20 -0
- package/dist/~internal/adapter/auth.mjs.map +1 -0
- package/dist/~internal/adapter/database.d.mts +240 -0
- package/dist/~internal/adapter/database.mjs +63 -0
- package/dist/~internal/adapter/database.mjs.map +1 -0
- package/dist/~internal/adapter/logger.d.mts +34 -0
- package/dist/~internal/adapter/logger.mjs +13 -0
- package/dist/~internal/adapter/logger.mjs.map +1 -0
- package/dist/~internal/adapter/storage.d.mts +208 -0
- package/dist/~internal/adapter/storage.mjs +63 -0
- package/dist/~internal/adapter/storage.mjs.map +1 -0
- package/dist/~internal/adapter/ui.d.mts +109 -0
- package/dist/~internal/adapter/ui.mjs +1 -0
- package/dist/~internal/adapter.d.mts +8 -0
- package/dist/~internal/adapter.mjs +6 -0
- package/dist/~internal/constants.d.mts +24 -0
- package/dist/~internal/constants.mjs +32 -0
- package/dist/~internal/constants.mjs.map +1 -0
- package/dist/~internal/mimes.d.mts +449 -0
- package/dist/~internal/mimes.mjs +454 -0
- package/dist/~internal/mimes.mjs.map +1 -0
- package/dist/~internal/router.d.mts +1651 -0
- package/dist/~internal/router.mjs +39 -0
- package/dist/~internal/router.mjs.map +1 -0
- package/dist/~internal/types.d.mts +77 -0
- package/dist/~internal/types.mjs +1 -0
- package/dist/~internal/utils.d.mts +4 -0
- package/dist/~internal/utils.mjs +5 -0
- package/openapi.json +3162 -0
- package/package.json +148 -27
- package/src/adapters/_internal/auth.ts +135 -0
- package/src/adapters/_internal/database.ts +241 -0
- package/src/adapters/_internal/index.ts +8 -0
- package/src/adapters/_internal/logger.ts +41 -0
- package/src/adapters/_internal/queue.ts +151 -0
- package/src/adapters/_internal/storage.ts +197 -0
- package/src/adapters/_internal/ui.ts +103 -0
- package/src/adapters/aws-dynamodb.ts +201 -0
- package/src/adapters/aws-s3.ts +160 -0
- package/src/adapters/azure-blob-storage.ts +223 -0
- package/src/adapters/azure-cosmos-db.ts +158 -0
- package/src/adapters/azure-data-tables.ts +223 -0
- package/src/adapters/azure-easy-auth.ts +174 -0
- package/src/adapters/azure-functions.ts +242 -0
- package/src/adapters/fs.ts +398 -0
- package/src/adapters/gcp-big-table.ts +157 -0
- package/src/adapters/gcp-firestore.ts +146 -0
- package/src/adapters/gcp-storage.ts +141 -0
- package/src/adapters/mysql.ts +296 -0
- package/src/adapters/redis.ts +242 -0
- package/src/handlers/handle-process-zip.ts +117 -0
- package/src/handlers/handle-purge.ts +65 -0
- package/src/handlers/handle-serve-storybook.ts +101 -0
- package/src/index.ts +81 -16
- package/src/mocks/mock-auth-service.ts +51 -0
- package/src/mocks/mock-store.ts +26 -0
- package/src/models/builds-model.ts +373 -0
- package/src/models/builds-schema.ts +84 -0
- package/src/models/projects-model.ts +177 -0
- package/src/models/projects-schema.ts +69 -0
- package/src/models/tags-model.ts +138 -0
- package/src/models/tags-schema.ts +45 -0
- package/src/models/~model.ts +79 -0
- package/src/models/~shared-schema.ts +14 -0
- package/src/routers/_app-router.ts +57 -0
- package/src/routers/account-router.ts +136 -0
- package/src/routers/builds-router.ts +464 -0
- package/src/routers/projects-router.ts +309 -0
- package/src/routers/root-router.ts +127 -0
- package/src/routers/tags-router.ts +339 -0
- package/src/routers/tasks-router.ts +75 -0
- package/src/types.ts +107 -0
- package/src/urls.ts +327 -0
- package/src/utils/adapter-utils.ts +26 -0
- package/src/utils/auth.test.ts +71 -0
- package/src/utils/auth.ts +39 -0
- package/src/utils/constants.ts +31 -0
- package/src/utils/date-utils.ts +10 -0
- package/src/utils/error.test.ts +86 -0
- package/src/utils/error.ts +140 -0
- package/src/utils/file-utils.test.ts +65 -0
- package/src/utils/file-utils.ts +43 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/mime-utils.ts +457 -0
- package/src/utils/openapi-utils.ts +49 -0
- package/src/utils/request.ts +97 -0
- package/src/utils/response.ts +20 -0
- package/src/utils/store.ts +85 -0
- package/src/utils/story-utils.ts +42 -0
- package/src/utils/text-utils.ts +10 -0
- package/src/utils/ui-utils.ts +57 -0
- package/src/utils/url-utils.ts +113 -0
- package/dist/index.js +0 -554
- package/src/commands/create.ts +0 -263
- package/src/commands/purge.ts +0 -70
- package/src/commands/test.ts +0 -42
- package/src/service-schema.d.ts +0 -2023
- package/src/utils/auth-utils.ts +0 -31
- package/src/utils/pkg-utils.ts +0 -37
- package/src/utils/sb-build.ts +0 -55
- package/src/utils/sb-test.ts +0 -115
- package/src/utils/schema-utils.ts +0 -123
- package/src/utils/stream-utils.ts +0 -72
- package/src/utils/types.ts +0 -4
- package/src/utils/zip.ts +0 -77
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createConsoleLoggerAdapter } from "../~internal/adapter/logger.mjs";
|
|
2
|
+
import { DEFAULT_LOCALE } from "../~internal/constants.mjs";
|
|
3
|
+
import { SuperHeaders } from "@remix-run/headers";
|
|
4
|
+
import { endTime, startTime } from "hono/timing";
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
+
|
|
7
|
+
//#region src/utils/store.ts
|
|
8
|
+
const localStore = new AsyncLocalStorage();
|
|
9
|
+
function getStore() {
|
|
10
|
+
const value = localStore.getStore();
|
|
11
|
+
if (!value) throw new Error("Store not found.");
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function getStoreOrNull() {
|
|
15
|
+
const value = localStore.getStore();
|
|
16
|
+
if (!value) return null;
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function setupStore(options, initPromises) {
|
|
20
|
+
return async (ctx, next) => {
|
|
21
|
+
const logger = options.logger ?? createConsoleLoggerAdapter();
|
|
22
|
+
startTime(ctx, "init-adapters", "Initialize adapters");
|
|
23
|
+
await initPromises;
|
|
24
|
+
endTime(ctx, "init-adapters");
|
|
25
|
+
const request = ctx.req.raw;
|
|
26
|
+
const headers = new SuperHeaders(request.headers);
|
|
27
|
+
const locale = headers.acceptLanguage.languages[0] ?? DEFAULT_LOCALE;
|
|
28
|
+
startTime(ctx, "get-user", "Get user details");
|
|
29
|
+
const user = await options.auth?.getUserDetails({
|
|
30
|
+
logger,
|
|
31
|
+
request: request.clone()
|
|
32
|
+
});
|
|
33
|
+
endTime(ctx, "get-user");
|
|
34
|
+
startTime(ctx, "request-handler", "Handle request");
|
|
35
|
+
localStore.enterWith({
|
|
36
|
+
...options,
|
|
37
|
+
abortSignal: request.signal,
|
|
38
|
+
auth: options.auth,
|
|
39
|
+
headers,
|
|
40
|
+
locale,
|
|
41
|
+
logger,
|
|
42
|
+
prefix: options.config?.prefix ?? "",
|
|
43
|
+
request,
|
|
44
|
+
url: request.url,
|
|
45
|
+
user
|
|
46
|
+
});
|
|
47
|
+
await next();
|
|
48
|
+
endTime(ctx, "request-handler");
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
export { getStore, getStoreOrNull, localStore, setupStore };
|
|
54
|
+
//# sourceMappingURL=store.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.mjs","names":[],"sources":["../../src/utils/store.ts"],"sourcesContent":["import { SuperHeaders } from \"@remix-run/headers\";\nimport type { MiddlewareHandler } from \"hono\";\nimport { endTime, startTime } from \"hono/timing\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n createConsoleLoggerAdapter,\n type AuthAdapter,\n type LoggerAdapter,\n} from \"../adapters/_internal/index.ts\";\nimport type { RouterOptions, StoryBookerUser } from \"../types.ts\";\nimport type { ErrorParser } from \"../utils/error.ts\";\nimport { DEFAULT_LOCALE } from \"./constants.ts\";\n\nexport interface Store extends RouterOptions<StoryBookerUser> {\n abortSignal: AbortSignal | undefined;\n errorParser?: ErrorParser;\n headers: SuperHeaders;\n locale: string;\n logger: LoggerAdapter;\n prefix: string;\n request: Request;\n url: string;\n user: StoryBookerUser | null | undefined;\n}\n\nexport const localStore = new AsyncLocalStorage<Store>();\n\nexport function getStore(): Store {\n const value = localStore.getStore();\n if (!value) {\n throw new Error(\"Store not found.\");\n }\n\n return value;\n}\n\nexport function getStoreOrNull(): Store | null {\n const value = localStore.getStore();\n if (!value) {\n return null;\n }\n\n return value;\n}\n\nexport function setupStore<User extends StoryBookerUser>(\n options: RouterOptions<User>,\n initPromises: Promise<unknown>,\n): MiddlewareHandler {\n return async (ctx, next) => {\n const logger = options.logger ?? createConsoleLoggerAdapter();\n\n startTime(ctx, \"init-adapters\", \"Initialize adapters\");\n await initPromises;\n endTime(ctx, \"init-adapters\");\n\n const request = ctx.req.raw;\n const headers = new SuperHeaders(request.headers);\n const locale = headers.acceptLanguage.languages[0] ?? DEFAULT_LOCALE;\n\n startTime(ctx, \"get-user\", \"Get user details\");\n const user = await options.auth?.getUserDetails({\n logger,\n request: request.clone(),\n });\n endTime(ctx, \"get-user\");\n\n startTime(ctx, \"request-handler\", \"Handle request\");\n localStore.enterWith({\n ...options,\n abortSignal: request.signal,\n auth: options.auth as AuthAdapter | undefined,\n headers,\n locale,\n logger,\n prefix: options.config?.prefix ?? \"\",\n request,\n url: request.url,\n user,\n });\n\n await next();\n endTime(ctx, \"request-handler\");\n };\n}\n"],"mappings":";;;;;;;AAyBA,MAAa,aAAa,IAAI,mBAA0B;AAExD,SAAgB,WAAkB;CAChC,MAAM,QAAQ,WAAW,UAAU;AACnC,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,mBAAmB;AAGrC,QAAO;;AAGT,SAAgB,iBAA+B;CAC7C,MAAM,QAAQ,WAAW,UAAU;AACnC,KAAI,CAAC,MACH,QAAO;AAGT,QAAO;;AAGT,SAAgB,WACd,SACA,cACmB;AACnB,QAAO,OAAO,KAAK,SAAS;EAC1B,MAAM,SAAS,QAAQ,UAAU,4BAA4B;AAE7D,YAAU,KAAK,iBAAiB,sBAAsB;AACtD,QAAM;AACN,UAAQ,KAAK,gBAAgB;EAE7B,MAAM,UAAU,IAAI,IAAI;EACxB,MAAM,UAAU,IAAI,aAAa,QAAQ,QAAQ;EACjD,MAAM,SAAS,QAAQ,eAAe,UAAU,MAAM;AAEtD,YAAU,KAAK,YAAY,mBAAmB;EAC9C,MAAM,OAAO,MAAM,QAAQ,MAAM,eAAe;GAC9C;GACA,SAAS,QAAQ,OAAO;GACzB,CAAC;AACF,UAAQ,KAAK,WAAW;AAExB,YAAU,KAAK,mBAAmB,iBAAiB;AACnD,aAAW,UAAU;GACnB,GAAG;GACH,aAAa,QAAQ;GACrB,MAAM,QAAQ;GACd;GACA;GACA;GACA,QAAQ,QAAQ,QAAQ,UAAU;GAClC;GACA,KAAK,QAAQ;GACb;GACD,CAAC;AAEF,QAAM,MAAM;AACZ,UAAQ,KAAK,kBAAkB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getStore } from "./store.mjs";
|
|
2
|
+
import { urlBuilderWithoutStore } from "../urls.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/ui-utils.ts
|
|
5
|
+
function createUIAdapterOptions() {
|
|
6
|
+
const { auth, database, storage, ui, locale, logger, url, user } = getStore();
|
|
7
|
+
return {
|
|
8
|
+
isAuthEnabled: Boolean(auth),
|
|
9
|
+
locale,
|
|
10
|
+
logger,
|
|
11
|
+
url,
|
|
12
|
+
urlBuilder: urlBuilderWithoutStore,
|
|
13
|
+
user,
|
|
14
|
+
adaptersMetadata: {
|
|
15
|
+
auth: auth?.metadata,
|
|
16
|
+
database: database?.metadata,
|
|
17
|
+
logger: logger?.metadata,
|
|
18
|
+
storage: storage?.metadata,
|
|
19
|
+
ui: ui?.metadata
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function createUIResultResponse(ctx, render, props, init) {
|
|
24
|
+
return uiResultResponse(ctx, render(props, createUIAdapterOptions()), init);
|
|
25
|
+
}
|
|
26
|
+
async function uiResultResponse(ctx, result, init) {
|
|
27
|
+
if (result instanceof Promise) {
|
|
28
|
+
const awaitedResult = await result;
|
|
29
|
+
if (awaitedResult instanceof Response) return awaitedResult;
|
|
30
|
+
return ctx.html(awaitedResult);
|
|
31
|
+
}
|
|
32
|
+
if (result instanceof Response) return result;
|
|
33
|
+
return ctx.html(result, init);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { createUIAdapterOptions, createUIResultResponse, uiResultResponse };
|
|
38
|
+
//# sourceMappingURL=ui-utils.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-utils.mjs","names":[],"sources":["../../src/utils/ui-utils.ts"],"sourcesContent":["import type { Context } from \"hono\";\nimport type { UIAdapterOptions, UIResult } from \"../adapters/_internal/ui.ts\";\nimport { urlBuilderWithoutStore } from \"../urls.ts\";\nimport { getStore } from \"./store.ts\";\n\nexport function createUIAdapterOptions(): UIAdapterOptions {\n const { auth, database, storage, ui, locale, logger, url, user } = getStore();\n\n return {\n isAuthEnabled: Boolean(auth),\n locale,\n logger,\n url,\n urlBuilder: urlBuilderWithoutStore,\n user,\n adaptersMetadata: {\n auth: auth?.metadata,\n database: database?.metadata,\n logger: logger?.metadata,\n storage: storage?.metadata,\n ui: ui?.metadata,\n },\n };\n}\n\n// oxlint-disable-next-line max-params\nexport function createUIResultResponse<Props>(\n ctx: Context,\n render: (props: Props, options: UIAdapterOptions) => UIResult,\n props: NoInfer<Props>,\n init?: ResInit,\n): Promise<Response> {\n return uiResultResponse(ctx, render(props, createUIAdapterOptions()), init);\n}\n\nexport async function uiResultResponse(\n ctx: Context,\n result: UIResult,\n init?: ResInit,\n): Promise<Response> {\n if (result instanceof Promise) {\n const awaitedResult = await result;\n if (awaitedResult instanceof Response) {\n return awaitedResult;\n }\n\n return ctx.html(awaitedResult);\n }\n\n if (result instanceof Response) {\n return result;\n }\n\n return ctx.html(result, init);\n}\n\ntype ResInit = Parameters<Context[\"html\"]>[1];\n"],"mappings":";;;;AAKA,SAAgB,yBAA2C;CACzD,MAAM,EAAE,MAAM,UAAU,SAAS,IAAI,QAAQ,QAAQ,KAAK,SAAS,UAAU;AAE7E,QAAO;EACL,eAAe,QAAQ,KAAK;EAC5B;EACA;EACA;EACA,YAAY;EACZ;EACA,kBAAkB;GAChB,MAAM,MAAM;GACZ,UAAU,UAAU;GACpB,QAAQ,QAAQ;GAChB,SAAS,SAAS;GAClB,IAAI,IAAI;GACT;EACF;;AAIH,SAAgB,uBACd,KACA,QACA,OACA,MACmB;AACnB,QAAO,iBAAiB,KAAK,OAAO,OAAO,wBAAwB,CAAC,EAAE,KAAK;;AAG7E,eAAsB,iBACpB,KACA,QACA,MACmB;AACnB,KAAI,kBAAkB,SAAS;EAC7B,MAAM,gBAAgB,MAAM;AAC5B,MAAI,yBAAyB,SAC3B,QAAO;AAGT,SAAO,IAAI,KAAK,cAAc;;AAGhC,KAAI,kBAAkB,SACpB,QAAO;AAGT,QAAO,IAAI,KAAK,QAAQ,KAAK"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import "../~internal/router.mjs";
|
|
2
|
+
import { hc } from "hono/client";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/url-utils.d.ts
|
|
5
|
+
|
|
6
|
+
declare function generatePrefixFromBaseRoute(baseRoute: string): `/${string}` | undefined;
|
|
7
|
+
declare function urlJoin(...parts: string[]): string;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { generatePrefixFromBaseRoute, urlJoin };
|
|
10
|
+
//# sourceMappingURL=url-utils.d.mts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { hc } from "hono/client";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/url-utils.ts
|
|
4
|
+
function linkRoute(link, options) {
|
|
5
|
+
const { baseUrl, searchParams } = options;
|
|
6
|
+
if (typeof link === "string") {
|
|
7
|
+
if (baseUrl !== void 0) return appendSearchParamsToURL(new URL(link, baseUrl), searchParams).toString();
|
|
8
|
+
return link;
|
|
9
|
+
}
|
|
10
|
+
if (link instanceof URL) return appendSearchParamsToURL(link, searchParams).toString();
|
|
11
|
+
if (baseUrl) return appendSearchParamsToURL(link(hc(baseUrl)), searchParams).toString();
|
|
12
|
+
const dummyBaseUrl = "http://host.local";
|
|
13
|
+
return appendSearchParamsToURL(link(hc(dummyBaseUrl)), searchParams).toString().replace(dummyBaseUrl, "");
|
|
14
|
+
}
|
|
15
|
+
function appendSearchParamsToURL(url, searchParams) {
|
|
16
|
+
searchParams?.forEach((value, key) => {
|
|
17
|
+
url.searchParams.append(key, value);
|
|
18
|
+
});
|
|
19
|
+
return url;
|
|
20
|
+
}
|
|
21
|
+
function generatePrefixFromBaseRoute(baseRoute) {
|
|
22
|
+
if (!baseRoute) return;
|
|
23
|
+
if (baseRoute.startsWith("/")) return baseRoute;
|
|
24
|
+
return `/${baseRoute}`;
|
|
25
|
+
}
|
|
26
|
+
function urlJoin(...parts) {
|
|
27
|
+
if (parts.length === 0) return "";
|
|
28
|
+
let protocol = "";
|
|
29
|
+
let host = "";
|
|
30
|
+
const match = parts[0]?.match(/^([a-zA-Z][a-zA-Z0-9+\-.]*:)?(\/\/[^/?#]*)?/);
|
|
31
|
+
if (match && (match[1] || match[2])) {
|
|
32
|
+
protocol = match[1] ?? "";
|
|
33
|
+
host = match[2] ?? "";
|
|
34
|
+
const remainder = parts[0]?.slice((match[0] || "").length);
|
|
35
|
+
parts = remainder ? [remainder, ...parts.slice(1)] : parts.slice(1);
|
|
36
|
+
}
|
|
37
|
+
const segments = [];
|
|
38
|
+
for (const part of parts) for (const seg of part.split("/")) {
|
|
39
|
+
if (!seg || seg === ".") continue;
|
|
40
|
+
if (seg === "..") {
|
|
41
|
+
if (segments.length > 0) segments.pop();
|
|
42
|
+
} else segments.push(seg);
|
|
43
|
+
}
|
|
44
|
+
let result = "";
|
|
45
|
+
if (protocol || host) {
|
|
46
|
+
result = protocol + host;
|
|
47
|
+
if (segments.length > 0) result += segments[0]?.startsWith("?") || segments[0]?.startsWith("#") ? "" : `/${segments.join("/")}`;
|
|
48
|
+
} else result = `/${segments.join("/")}`;
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
export { generatePrefixFromBaseRoute, linkRoute, urlJoin };
|
|
54
|
+
//# sourceMappingURL=url-utils.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-utils.mjs","names":["segments: string[]"],"sources":["../../src/utils/url-utils.ts"],"sourcesContent":["import { hc } from \"hono/client\";\nimport type { AppRouter } from \"../routers/_app-router\";\n\ntype HonoClient = ReturnType<typeof hc<AppRouter>>;\n\nexport function linkRoute(\n link: ((client: HonoClient) => URL) | URL | string,\n options: { baseUrl?: string; searchParams?: URLSearchParams },\n): string {\n const { baseUrl, searchParams } = options;\n\n if (typeof link === \"string\") {\n if (baseUrl !== undefined) {\n return appendSearchParamsToURL(new URL(link, baseUrl), searchParams).toString();\n }\n return link;\n }\n\n if (link instanceof URL) {\n return appendSearchParamsToURL(link, searchParams).toString();\n }\n\n if (baseUrl) {\n const client = hc<AppRouter>(baseUrl);\n\n return appendSearchParamsToURL(link(client), searchParams).toString();\n }\n\n const dummyBaseUrl = \"http://host.local\";\n const client = hc<AppRouter>(dummyBaseUrl);\n const url = appendSearchParamsToURL(link(client), searchParams).toString();\n return url.replace(dummyBaseUrl, \"\");\n}\n\nfunction appendSearchParamsToURL(url: URL, searchParams: URLSearchParams | undefined): URL {\n // oxlint-disable-next-line no-array-for-each\n searchParams?.forEach((value, key) => {\n url.searchParams.append(key, value);\n });\n\n return url;\n}\n\nexport function urlSearchParamsToObject(\n query: URLSearchParams | FormData,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const key of query.keys()) {\n const values = query.getAll(key);\n // If multiple values for the same key, return array, else single value string\n result[key] = values.length > 1 ? values : values[0];\n }\n\n return result;\n}\n\nexport function generatePrefixFromBaseRoute(baseRoute: string): `/${string}` | undefined {\n if (!baseRoute) {\n return undefined;\n }\n if (baseRoute.startsWith(\"/\")) {\n return baseRoute as `/${string}`;\n }\n return `/${baseRoute}` as const;\n}\n\nexport function urlJoin(...parts: string[]): string {\n if (parts.length === 0) {\n return \"\";\n }\n let protocol = \"\";\n let host = \"\";\n // Detect protocol + host in the first argument if present\n const match = parts[0]?.match(/^([a-zA-Z][a-zA-Z0-9+\\-.]*:)?(\\/\\/[^/?#]*)?/);\n if (match && (match[1] || match[2])) {\n protocol = match[1] ?? \"\"; // e.g. \"https:\"\n host = match[2] ?? \"\"; // e.g. \"//example.com\"\n const remainder = parts[0]?.slice((match[0] || \"\").length);\n parts = remainder ? [remainder, ...parts.slice(1)] : parts.slice(1);\n }\n const segments: string[] = [];\n for (const part of parts) {\n for (const seg of part.split(\"/\")) {\n if (!seg || seg === \".\") {\n continue;\n }\n if (seg === \"..\") {\n // Don't pop past the beginning\n if (segments.length > 0) {\n segments.pop();\n }\n } else {\n segments.push(seg);\n }\n }\n }\n // Build the final URL\n let result = \"\";\n if (protocol || host) {\n result = protocol + host;\n if (segments.length > 0) {\n // Ensure separator slash between host and path\n result +=\n segments[0]?.startsWith(\"?\") || segments[0]?.startsWith(\"#\")\n ? \"\"\n : `/${segments.join(\"/\")}`;\n }\n } else {\n result = `/${segments.join(\"/\")}`;\n }\n return result;\n}\n"],"mappings":";;;AAKA,SAAgB,UACd,MACA,SACQ;CACR,MAAM,EAAE,SAAS,iBAAiB;AAElC,KAAI,OAAO,SAAS,UAAU;AAC5B,MAAI,YAAY,OACd,QAAO,wBAAwB,IAAI,IAAI,MAAM,QAAQ,EAAE,aAAa,CAAC,UAAU;AAEjF,SAAO;;AAGT,KAAI,gBAAgB,IAClB,QAAO,wBAAwB,MAAM,aAAa,CAAC,UAAU;AAG/D,KAAI,QAGF,QAAO,wBAAwB,KAFhB,GAAc,QAAQ,CAEM,EAAE,aAAa,CAAC,UAAU;CAGvE,MAAM,eAAe;AAGrB,QADY,wBAAwB,KADrB,GAAc,aAAa,CACM,EAAE,aAAa,CAAC,UAAU,CAC/D,QAAQ,cAAc,GAAG;;AAGtC,SAAS,wBAAwB,KAAU,cAAgD;AAEzF,eAAc,SAAS,OAAO,QAAQ;AACpC,MAAI,aAAa,OAAO,KAAK,MAAM;GACnC;AAEF,QAAO;;AAiBT,SAAgB,4BAA4B,WAA6C;AACvF,KAAI,CAAC,UACH;AAEF,KAAI,UAAU,WAAW,IAAI,CAC3B,QAAO;AAET,QAAO,IAAI;;AAGb,SAAgB,QAAQ,GAAG,OAAyB;AAClD,KAAI,MAAM,WAAW,EACnB,QAAO;CAET,IAAI,WAAW;CACf,IAAI,OAAO;CAEX,MAAM,QAAQ,MAAM,IAAI,MAAM,8CAA8C;AAC5E,KAAI,UAAU,MAAM,MAAM,MAAM,KAAK;AACnC,aAAW,MAAM,MAAM;AACvB,SAAO,MAAM,MAAM;EACnB,MAAM,YAAY,MAAM,IAAI,OAAO,MAAM,MAAM,IAAI,OAAO;AAC1D,UAAQ,YAAY,CAAC,WAAW,GAAG,MAAM,MAAM,EAAE,CAAC,GAAG,MAAM,MAAM,EAAE;;CAErE,MAAMA,WAAqB,EAAE;AAC7B,MAAK,MAAM,QAAQ,MACjB,MAAK,MAAM,OAAO,KAAK,MAAM,IAAI,EAAE;AACjC,MAAI,CAAC,OAAO,QAAQ,IAClB;AAEF,MAAI,QAAQ,MAEV;OAAI,SAAS,SAAS,EACpB,UAAS,KAAK;QAGhB,UAAS,KAAK,IAAI;;CAKxB,IAAI,SAAS;AACb,KAAI,YAAY,MAAM;AACpB,WAAS,WAAW;AACpB,MAAI,SAAS,SAAS,EAEpB,WACE,SAAS,IAAI,WAAW,IAAI,IAAI,SAAS,IAAI,WAAW,IAAI,GACxD,KACA,IAAI,SAAS,KAAK,IAAI;OAG9B,UAAS,IAAI,SAAS,KAAK,IAAI;AAEjC,QAAO"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { StoryBookerAdapterMetadata } from "../../utils/adapter-utils.mjs";
|
|
2
|
+
import { LoggerAdapter } from "./logger.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/_internal/auth.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Service adapter to manage authentication.
|
|
8
|
+
*
|
|
9
|
+
* The service is responsible to authorise users from
|
|
10
|
+
* accessing the app.
|
|
11
|
+
*/
|
|
12
|
+
interface AuthAdapter<AuthUser extends StoryBookerUser = StoryBookerUser> {
|
|
13
|
+
/**
|
|
14
|
+
* Metadata about the adapter.
|
|
15
|
+
*/
|
|
16
|
+
metadata: StoryBookerAdapterMetadata;
|
|
17
|
+
/**
|
|
18
|
+
* An optional method that is called on app boot-up
|
|
19
|
+
* to run async setup functions.
|
|
20
|
+
* @param options Common options like abortSignal.
|
|
21
|
+
* @throws if an error occur during initialisation.
|
|
22
|
+
*/
|
|
23
|
+
init?: (options: Omit<AuthAdapterOptions, "request">) => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Get details about the user and permissions based on incoming request.
|
|
26
|
+
*
|
|
27
|
+
* @param options Common options like abortSignal.
|
|
28
|
+
*
|
|
29
|
+
* @throws an error or response (redirect-to-login) if it is a unauthenticated/unauthorised request.
|
|
30
|
+
*/
|
|
31
|
+
getUserDetails: (options: AuthAdapterOptions) => Promise<AuthUser | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Get user to login from UI. The returning response should create auth session.
|
|
34
|
+
* User will be redirected automatically after function is resolved.
|
|
35
|
+
*
|
|
36
|
+
* @param options Common options like abortSignal.
|
|
37
|
+
*/
|
|
38
|
+
login: (options: AuthAdapterOptions) => Promise<Response> | Response;
|
|
39
|
+
/**
|
|
40
|
+
* Get user to logout from UI. The returning response should clear auth session.
|
|
41
|
+
* User will be redirected automatically after function is resolved.
|
|
42
|
+
*
|
|
43
|
+
* @param options Common options like abortSignal.
|
|
44
|
+
*/
|
|
45
|
+
logout: (user: AuthUser, options: AuthAdapterOptions) => Promise<Response> | Response;
|
|
46
|
+
/**
|
|
47
|
+
* Render custom HTML in account page. Must return valid HTML string;
|
|
48
|
+
*
|
|
49
|
+
* @param options Common options like abortSignal.
|
|
50
|
+
*/
|
|
51
|
+
renderAccountDetails?: (user: AuthUser, options: AuthAdapterOptions) => Promise<string> | string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Type for the callback function to check permissions.
|
|
55
|
+
*
|
|
56
|
+
* @param options Common options like abortSignal.
|
|
57
|
+
*
|
|
58
|
+
* @returns true to allow access, or following to deny:
|
|
59
|
+
* - false - returns 403 response
|
|
60
|
+
* - Response - returns the specified HTTP response
|
|
61
|
+
*/
|
|
62
|
+
type AuthAdapterAuthorise<AuthUser extends StoryBookerUser = StoryBookerUser> = (params: {
|
|
63
|
+
permission: StoryBookerPermissionWithKey;
|
|
64
|
+
user: AuthUser;
|
|
65
|
+
}, options: AuthAdapterOptions) => Promise<boolean | Response> | boolean | Response;
|
|
66
|
+
/** Type of permission to check */
|
|
67
|
+
interface StoryBookerPermission {
|
|
68
|
+
action: StoryBookerPermissionAction;
|
|
69
|
+
projectId?: string;
|
|
70
|
+
resource: StoryBookerPermissionResource;
|
|
71
|
+
}
|
|
72
|
+
/** Permission object with key */
|
|
73
|
+
type StoryBookerPermissionWithKey = StoryBookerPermission & {
|
|
74
|
+
key: StoryBookerPermissionKey;
|
|
75
|
+
};
|
|
76
|
+
/** Permission in a string format */
|
|
77
|
+
type StoryBookerPermissionKey = `${StoryBookerPermissionResource}:${StoryBookerPermissionAction}`;
|
|
78
|
+
/** Type of possible resources to check permissions for */
|
|
79
|
+
type StoryBookerPermissionResource = "project" | "build" | "tag";
|
|
80
|
+
/** Type of possible actions to check permissions for */
|
|
81
|
+
type StoryBookerPermissionAction = "create" | "read" | "update" | "delete";
|
|
82
|
+
/**
|
|
83
|
+
* Base representation of a generic User
|
|
84
|
+
*/
|
|
85
|
+
interface StoryBookerUser {
|
|
86
|
+
/** Name of the user displayed in UI. */
|
|
87
|
+
displayName: string;
|
|
88
|
+
/** Unique ID of the user. Could be email-address. */
|
|
89
|
+
id: string;
|
|
90
|
+
/** Static URL for User's avatar shown in UI. */
|
|
91
|
+
imageUrl?: string;
|
|
92
|
+
/** Title or Team-name of the User shown in UI. */
|
|
93
|
+
title?: string;
|
|
94
|
+
/** Permissions assigned to the user. Missing permissions are considered false. */
|
|
95
|
+
permissions: Partial<Record<StoryBookerPermissionKey, boolean>>;
|
|
96
|
+
}
|
|
97
|
+
/** Common Auth adapter options. */
|
|
98
|
+
interface AuthAdapterOptions {
|
|
99
|
+
/** A signal that can be used to cancel the request handling. */
|
|
100
|
+
abortSignal?: AbortSignal;
|
|
101
|
+
/** Incoming request (cloned) */
|
|
102
|
+
request: Request;
|
|
103
|
+
/** Logger */
|
|
104
|
+
logger: LoggerAdapter;
|
|
105
|
+
}
|
|
106
|
+
declare const StoryBookerPermissionsAllEnabled: {
|
|
107
|
+
"build:create": true;
|
|
108
|
+
"build:delete": true;
|
|
109
|
+
"project:update": true;
|
|
110
|
+
"build:read": true;
|
|
111
|
+
"tag:delete": true;
|
|
112
|
+
"build:update": true;
|
|
113
|
+
"tag:create": true;
|
|
114
|
+
"project:read": true;
|
|
115
|
+
"project:create": true;
|
|
116
|
+
"tag:read": true;
|
|
117
|
+
"project:delete": true;
|
|
118
|
+
"tag:update": true;
|
|
119
|
+
};
|
|
120
|
+
declare const StoryBookerPermissionsList: ("build:create" | "build:delete" | "build:read" | "build:update" | "project:create" | "project:delete" | "project:read" | "project:update" | "tag:create" | "tag:delete" | "tag:read" | "tag:update")[];
|
|
121
|
+
//#endregion
|
|
122
|
+
export { AuthAdapter, AuthAdapterAuthorise, AuthAdapterOptions, StoryBookerPermission, StoryBookerPermissionAction, StoryBookerPermissionKey, StoryBookerPermissionResource, StoryBookerPermissionWithKey, StoryBookerPermissionsAllEnabled, StoryBookerPermissionsList, StoryBookerUser };
|
|
123
|
+
//# sourceMappingURL=auth.d.mts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/adapters/_internal/auth.ts
|
|
2
|
+
const StoryBookerPermissionsAllEnabled = {
|
|
3
|
+
"build:create": true,
|
|
4
|
+
"build:delete": true,
|
|
5
|
+
"project:update": true,
|
|
6
|
+
"build:read": true,
|
|
7
|
+
"tag:delete": true,
|
|
8
|
+
"build:update": true,
|
|
9
|
+
"tag:create": true,
|
|
10
|
+
"project:read": true,
|
|
11
|
+
"project:create": true,
|
|
12
|
+
"tag:read": true,
|
|
13
|
+
"project:delete": true,
|
|
14
|
+
"tag:update": true
|
|
15
|
+
};
|
|
16
|
+
const StoryBookerPermissionsList = Object.keys(StoryBookerPermissionsAllEnabled);
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { StoryBookerPermissionsAllEnabled, StoryBookerPermissionsList };
|
|
20
|
+
//# sourceMappingURL=auth.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.mjs","names":[],"sources":["../../../src/adapters/_internal/auth.ts"],"sourcesContent":["import type { StoryBookerAdapterMetadata } from \"../../utils/adapter-utils.ts\";\nimport type { LoggerAdapter } from \"./logger.ts\";\n\n/**\n * Service adapter to manage authentication.\n *\n * The service is responsible to authorise users from\n * accessing the app.\n */\nexport interface AuthAdapter<AuthUser extends StoryBookerUser = StoryBookerUser> {\n /**\n * Metadata about the adapter.\n */\n metadata: StoryBookerAdapterMetadata;\n\n /**\n * An optional method that is called on app boot-up\n * to run async setup functions.\n * @param options Common options like abortSignal.\n * @throws if an error occur during initialisation.\n */\n init?: (options: Omit<AuthAdapterOptions, \"request\">) => Promise<void>;\n\n /**\n * Get details about the user and permissions based on incoming request.\n *\n * @param options Common options like abortSignal.\n *\n * @throws an error or response (redirect-to-login) if it is a unauthenticated/unauthorised request.\n */\n getUserDetails: (options: AuthAdapterOptions) => Promise<AuthUser | null>;\n\n /**\n * Get user to login from UI. The returning response should create auth session.\n * User will be redirected automatically after function is resolved.\n *\n * @param options Common options like abortSignal.\n */\n login: (options: AuthAdapterOptions) => Promise<Response> | Response;\n\n /**\n * Get user to logout from UI. The returning response should clear auth session.\n * User will be redirected automatically after function is resolved.\n *\n * @param options Common options like abortSignal.\n */\n logout: (user: AuthUser, options: AuthAdapterOptions) => Promise<Response> | Response;\n\n /**\n * Render custom HTML in account page. Must return valid HTML string;\n *\n * @param options Common options like abortSignal.\n */\n renderAccountDetails?: (user: AuthUser, options: AuthAdapterOptions) => Promise<string> | string;\n}\n\n/**\n * Type for the callback function to check permissions.\n *\n * @param options Common options like abortSignal.\n *\n * @returns true to allow access, or following to deny:\n * - false - returns 403 response\n * - Response - returns the specified HTTP response\n */\nexport type AuthAdapterAuthorise<AuthUser extends StoryBookerUser = StoryBookerUser> = (\n params: {\n permission: StoryBookerPermissionWithKey;\n user: AuthUser;\n },\n options: AuthAdapterOptions,\n) => Promise<boolean | Response> | boolean | Response;\n\n/** Type of permission to check */\nexport interface StoryBookerPermission {\n action: StoryBookerPermissionAction;\n projectId?: string;\n resource: StoryBookerPermissionResource;\n}\n/** Permission object with key */\nexport type StoryBookerPermissionWithKey = StoryBookerPermission & {\n key: StoryBookerPermissionKey;\n};\n/** Permission in a string format */\nexport type StoryBookerPermissionKey =\n `${StoryBookerPermissionResource}:${StoryBookerPermissionAction}`;\n/** Type of possible resources to check permissions for */\nexport type StoryBookerPermissionResource = \"project\" | \"build\" | \"tag\";\n/** Type of possible actions to check permissions for */\nexport type StoryBookerPermissionAction = \"create\" | \"read\" | \"update\" | \"delete\";\n\n/**\n * Base representation of a generic User\n */\nexport interface StoryBookerUser {\n /** Name of the user displayed in UI. */\n displayName: string;\n /** Unique ID of the user. Could be email-address. */\n id: string;\n /** Static URL for User's avatar shown in UI. */\n imageUrl?: string;\n /** Title or Team-name of the User shown in UI. */\n title?: string;\n /** Permissions assigned to the user. Missing permissions are considered false. */\n permissions: Partial<Record<StoryBookerPermissionKey, boolean>>;\n}\n\n/** Common Auth adapter options. */\nexport interface AuthAdapterOptions {\n /** A signal that can be used to cancel the request handling. */\n abortSignal?: AbortSignal;\n /** Incoming request (cloned) */\n request: Request;\n /** Logger */\n logger: LoggerAdapter;\n}\n\nexport const StoryBookerPermissionsAllEnabled = {\n \"build:create\": true,\n \"build:delete\": true,\n \"project:update\": true,\n \"build:read\": true,\n \"tag:delete\": true,\n \"build:update\": true,\n \"tag:create\": true,\n \"project:read\": true,\n \"project:create\": true,\n \"tag:read\": true,\n \"project:delete\": true,\n \"tag:update\": true,\n} satisfies Record<StoryBookerPermissionKey, true>;\n\nexport const StoryBookerPermissionsList = Object.keys(\n StoryBookerPermissionsAllEnabled,\n) as StoryBookerPermissionKey[];\n"],"mappings":";AAqHA,MAAa,mCAAmC;CAC9C,gBAAgB;CAChB,gBAAgB;CAChB,kBAAkB;CAClB,cAAc;CACd,cAAc;CACd,gBAAgB;CAChB,cAAc;CACd,gBAAgB;CAChB,kBAAkB;CAClB,YAAY;CACZ,kBAAkB;CAClB,cAAc;CACf;AAED,MAAa,6BAA6B,OAAO,KAC/C,iCACD"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { StoryBookerAdapterMetadata } from "../../utils/adapter-utils.mjs";
|
|
2
|
+
import { LoggerAdapter } from "./logger.mjs";
|
|
3
|
+
import { ContentfulStatusCode } from "hono/utils/http-status";
|
|
4
|
+
|
|
5
|
+
//#region src/adapters/_internal/database.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Service adapter to interact with database.
|
|
9
|
+
*
|
|
10
|
+
* @description
|
|
11
|
+
* The adapter should provide callbacks to CRUD operations
|
|
12
|
+
* to an existing database.
|
|
13
|
+
*
|
|
14
|
+
* - `collection`: A collection/container/table to hold items.
|
|
15
|
+
* - `document`: A single entry in collection which contains key-value pairs (no nested data).
|
|
16
|
+
* Each document has a key 'id` which is unique in the collection.
|
|
17
|
+
*
|
|
18
|
+
* @throws {DatabaseNotInitializedError} if the DB service is not connected.
|
|
19
|
+
* @throws {CollectionAlreadyExistsError} if the collection already exists.
|
|
20
|
+
* @throws {CollectionDoesNotExistError} if the collection does not exist.
|
|
21
|
+
* @throws {DocumentAlreadyExistsError} if the document already exists in the collection.
|
|
22
|
+
* @throws {DocumentDoesNotExistError} if the document does not exist in the collection.
|
|
23
|
+
* @throws {CustomError} if some other error occurs.
|
|
24
|
+
*/
|
|
25
|
+
interface DatabaseAdapter<DbDocument extends StoryBookerDatabaseDocument = StoryBookerDatabaseDocument> {
|
|
26
|
+
/**
|
|
27
|
+
* Metadata about the adapter.
|
|
28
|
+
*/
|
|
29
|
+
metadata: StoryBookerAdapterMetadata;
|
|
30
|
+
/**
|
|
31
|
+
* An optional method that is called on app boot-up
|
|
32
|
+
* to run async setup functions.
|
|
33
|
+
* @param options Common options like abortSignal.
|
|
34
|
+
* @throws if an error occur during initialisation.
|
|
35
|
+
*/
|
|
36
|
+
init?: (options: DatabaseAdapterOptions) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* List all collections available in the DB.
|
|
39
|
+
* @param options Common options like abortSignal.
|
|
40
|
+
* @returns A list of names/IDs of the collections.
|
|
41
|
+
* @throws {DatabaseNotInitializedError} if the DB service is not connected.
|
|
42
|
+
*/
|
|
43
|
+
listCollections: (options: DatabaseAdapterOptions) => Promise<string[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Create a collection used for different projects.
|
|
46
|
+
* @param collectionId ID of the collection
|
|
47
|
+
* @param options Common options like abortSignal.
|
|
48
|
+
* @throws {DatabaseNotInitializedError} if the DB service is not connected.
|
|
49
|
+
* @throws {CollectionAlreadyExistsError} if collection with ID already exists.
|
|
50
|
+
*/
|
|
51
|
+
createCollection: (collectionId: string, options: DatabaseAdapterOptions) => Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Delete an existing collection.
|
|
54
|
+
* @param collectionId ID of the collection
|
|
55
|
+
* @param options Common options like abortSignal.
|
|
56
|
+
* @throws {DatabaseNotInitializedError} if the DB service is not connected.
|
|
57
|
+
* @throws {CollectionDoesNotExistError} if collection with ID does not exist.
|
|
58
|
+
*/
|
|
59
|
+
deleteCollection: (collectionId: string, options: DatabaseAdapterOptions) => Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Check if collection exists.
|
|
62
|
+
* @param collectionId ID of the collection
|
|
63
|
+
* @param options Common options like abortSignal.
|
|
64
|
+
* @returns if collection is available of not
|
|
65
|
+
* @throws never.
|
|
66
|
+
*/
|
|
67
|
+
hasCollection: (collectionId: string, options: DatabaseAdapterOptions) => Promise<boolean>;
|
|
68
|
+
/**
|
|
69
|
+
* List all documents available in the requested collection.
|
|
70
|
+
* @param collectionId ID of the collection
|
|
71
|
+
* @param listOptions Options to format/sort the result
|
|
72
|
+
* @param options Common options like abortSignal.
|
|
73
|
+
* @returns List of documents
|
|
74
|
+
* @throws if the collection does not exist.
|
|
75
|
+
*/
|
|
76
|
+
listDocuments: (collectionId: string, listOptions: DatabaseDocumentListOptions<DbDocument>, options: DatabaseAdapterOptions) => Promise<DbDocument[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Create a new document in the collection.
|
|
79
|
+
* @param collectionId ID of the collection
|
|
80
|
+
* @param documentData Data to be stored
|
|
81
|
+
* @param options Common options like abortSignal.
|
|
82
|
+
* @throws if the collection does not exist.
|
|
83
|
+
*/
|
|
84
|
+
createDocument: (collectionId: string, documentData: DbDocument, options: DatabaseAdapterOptions) => Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Get matching document data available in the requested collection.
|
|
87
|
+
* @param collectionId ID of the collection
|
|
88
|
+
* @param documentId ID of the document
|
|
89
|
+
* @param options Common options like abortSignal.
|
|
90
|
+
* @returns Data of the document
|
|
91
|
+
* @throws if the collection or document does not exist.
|
|
92
|
+
*/
|
|
93
|
+
getDocument: (collectionId: string, documentId: string, options: DatabaseAdapterOptions) => Promise<DbDocument>;
|
|
94
|
+
/**
|
|
95
|
+
* Check matching document data available in the requested collection.
|
|
96
|
+
* @param collectionId ID of the collection
|
|
97
|
+
* @param documentId ID of the document
|
|
98
|
+
* @param options Common options like abortSignal.
|
|
99
|
+
* @returns if document is available of not
|
|
100
|
+
* @throws if the collection does not exists.
|
|
101
|
+
*/
|
|
102
|
+
hasDocument: (collectionId: string, documentId: string, options: DatabaseAdapterOptions) => Promise<boolean>;
|
|
103
|
+
/**
|
|
104
|
+
* Update matching document data available in the requested collection.
|
|
105
|
+
* @param collectionId ID of the collection
|
|
106
|
+
* @param documentId ID of the document
|
|
107
|
+
* @param documentData Partial data to be updated.
|
|
108
|
+
* @param options Common options like abortSignal.
|
|
109
|
+
* @throws if the collection or document does not exist.
|
|
110
|
+
*/
|
|
111
|
+
updateDocument: (collectionId: string, documentId: string, documentData: Partial<Omit<DbDocument, "id">>, options: DatabaseAdapterOptions) => Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Delete matching document available in the requested collection.
|
|
114
|
+
* @param collectionId ID of the collection
|
|
115
|
+
* @param documentId ID of the document
|
|
116
|
+
* @param options Common options like abortSignal.
|
|
117
|
+
* @throws if the collection or document does not exist.
|
|
118
|
+
*/
|
|
119
|
+
deleteDocument: (collectionId: string, documentId: string, options: DatabaseAdapterOptions) => Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Base Document shape used in StoryBooker Database.
|
|
123
|
+
* Should always contain a filed 'id' with string value.
|
|
124
|
+
*/
|
|
125
|
+
interface StoryBookerDatabaseDocument {
|
|
126
|
+
id: string;
|
|
127
|
+
updatedAt: string;
|
|
128
|
+
createdAt: string;
|
|
129
|
+
[key: string]: unknown;
|
|
130
|
+
}
|
|
131
|
+
/** Common Database adapter options. */
|
|
132
|
+
interface DatabaseAdapterOptions {
|
|
133
|
+
/** A signal that can be used to cancel the request handling. */
|
|
134
|
+
abortSignal?: AbortSignal;
|
|
135
|
+
/** Logger */
|
|
136
|
+
logger: LoggerAdapter;
|
|
137
|
+
}
|
|
138
|
+
interface DatabaseDocumentListOptions<Item extends {
|
|
139
|
+
id: string;
|
|
140
|
+
}> {
|
|
141
|
+
limit?: number;
|
|
142
|
+
filter?: string | ((item: Item) => boolean);
|
|
143
|
+
select?: string[];
|
|
144
|
+
sort?: "latest" | ((item1: Item, item2: Item) => number);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Pre-defined Database adapter errors
|
|
148
|
+
* that can be used across different adapters.
|
|
149
|
+
*
|
|
150
|
+
* Throws {HTTPException} with relevant status codes.
|
|
151
|
+
*/
|
|
152
|
+
declare const DatabaseAdapterErrors: {
|
|
153
|
+
DatabaseNotInitializedError: {
|
|
154
|
+
new (cause?: unknown): {
|
|
155
|
+
name: string;
|
|
156
|
+
message: string;
|
|
157
|
+
stack?: string | undefined;
|
|
158
|
+
cause?: unknown;
|
|
159
|
+
readonly res?: Response | undefined;
|
|
160
|
+
readonly status: ContentfulStatusCode;
|
|
161
|
+
getResponse(): Response;
|
|
162
|
+
};
|
|
163
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void;
|
|
164
|
+
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
165
|
+
stackTraceLimit: number;
|
|
166
|
+
};
|
|
167
|
+
CollectionAlreadyExistsError: {
|
|
168
|
+
new (collectionId: string, cause?: unknown): {
|
|
169
|
+
name: string;
|
|
170
|
+
message: string;
|
|
171
|
+
stack?: string | undefined;
|
|
172
|
+
cause?: unknown;
|
|
173
|
+
readonly res?: Response | undefined;
|
|
174
|
+
readonly status: ContentfulStatusCode;
|
|
175
|
+
getResponse(): Response;
|
|
176
|
+
};
|
|
177
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void;
|
|
178
|
+
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
179
|
+
stackTraceLimit: number;
|
|
180
|
+
};
|
|
181
|
+
CollectionDoesNotExistError: {
|
|
182
|
+
new (collectionId: string, cause?: unknown): {
|
|
183
|
+
name: string;
|
|
184
|
+
message: string;
|
|
185
|
+
stack?: string | undefined;
|
|
186
|
+
cause?: unknown;
|
|
187
|
+
readonly res?: Response | undefined;
|
|
188
|
+
readonly status: ContentfulStatusCode;
|
|
189
|
+
getResponse(): Response;
|
|
190
|
+
};
|
|
191
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void;
|
|
192
|
+
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
193
|
+
stackTraceLimit: number;
|
|
194
|
+
};
|
|
195
|
+
DocumentAlreadyExistsError: {
|
|
196
|
+
new (collectionId: string, documentId: string, cause?: unknown): {
|
|
197
|
+
name: string;
|
|
198
|
+
message: string;
|
|
199
|
+
stack?: string | undefined;
|
|
200
|
+
cause?: unknown;
|
|
201
|
+
readonly res?: Response | undefined;
|
|
202
|
+
readonly status: ContentfulStatusCode;
|
|
203
|
+
getResponse(): Response;
|
|
204
|
+
};
|
|
205
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void;
|
|
206
|
+
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
207
|
+
stackTraceLimit: number;
|
|
208
|
+
};
|
|
209
|
+
DocumentDoesNotExistError: {
|
|
210
|
+
new (collectionId: string, documentId: string, cause?: unknown): {
|
|
211
|
+
name: string;
|
|
212
|
+
message: string;
|
|
213
|
+
stack?: string | undefined;
|
|
214
|
+
cause?: unknown;
|
|
215
|
+
readonly res?: Response | undefined;
|
|
216
|
+
readonly status: ContentfulStatusCode;
|
|
217
|
+
getResponse(): Response;
|
|
218
|
+
};
|
|
219
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void;
|
|
220
|
+
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
221
|
+
stackTraceLimit: number;
|
|
222
|
+
};
|
|
223
|
+
CustomError: {
|
|
224
|
+
new (status: number | undefined, message: string, cause?: unknown): {
|
|
225
|
+
name: string;
|
|
226
|
+
message: string;
|
|
227
|
+
stack?: string | undefined;
|
|
228
|
+
cause?: unknown;
|
|
229
|
+
readonly res?: Response | undefined;
|
|
230
|
+
readonly status: ContentfulStatusCode;
|
|
231
|
+
getResponse(): Response;
|
|
232
|
+
};
|
|
233
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void;
|
|
234
|
+
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
235
|
+
stackTraceLimit: number;
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
//#endregion
|
|
239
|
+
export { DatabaseAdapter, DatabaseAdapterErrors, DatabaseAdapterOptions, DatabaseDocumentListOptions, StoryBookerDatabaseDocument };
|
|
240
|
+
//# sourceMappingURL=database.d.mts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { HTTPException } from "hono/http-exception";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/_internal/database.ts
|
|
4
|
+
/**
|
|
5
|
+
* Pre-defined Database adapter errors
|
|
6
|
+
* that can be used across different adapters.
|
|
7
|
+
*
|
|
8
|
+
* Throws {HTTPException} with relevant status codes.
|
|
9
|
+
*/
|
|
10
|
+
const DatabaseAdapterErrors = {
|
|
11
|
+
DatabaseNotInitializedError: class extends HTTPException {
|
|
12
|
+
constructor(cause) {
|
|
13
|
+
super(500, {
|
|
14
|
+
cause,
|
|
15
|
+
message: "Database adapter is not initialized."
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
CollectionAlreadyExistsError: class extends HTTPException {
|
|
20
|
+
constructor(collectionId, cause) {
|
|
21
|
+
super(409, {
|
|
22
|
+
cause,
|
|
23
|
+
message: `Database collection '${collectionId}' already exists.`
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
CollectionDoesNotExistError: class extends HTTPException {
|
|
28
|
+
constructor(collectionId, cause) {
|
|
29
|
+
super(404, {
|
|
30
|
+
cause,
|
|
31
|
+
message: `Database collection '${collectionId}' does not exist.`
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
DocumentAlreadyExistsError: class extends HTTPException {
|
|
36
|
+
constructor(collectionId, documentId, cause) {
|
|
37
|
+
super(409, {
|
|
38
|
+
cause,
|
|
39
|
+
message: `Database document '${documentId}' already exists in collection '${collectionId}'.`
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
DocumentDoesNotExistError: class extends HTTPException {
|
|
44
|
+
constructor(collectionId, documentId, cause) {
|
|
45
|
+
super(404, {
|
|
46
|
+
cause,
|
|
47
|
+
message: `Database document '${documentId}' does not exist in collection '${collectionId}'.`
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
CustomError: class extends HTTPException {
|
|
52
|
+
constructor(status, message, cause) {
|
|
53
|
+
super(status, {
|
|
54
|
+
cause,
|
|
55
|
+
message
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { DatabaseAdapterErrors };
|
|
63
|
+
//# sourceMappingURL=database.mjs.map
|