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 @@
|
|
|
1
|
+
{"version":3,"file":"azure-blob-storage.mjs","names":["#client","containers: string[]","blobClientsToDelete: BlobClient[]","promises: Promise<Result>[]","errors: unknown[]"],"sources":["../src/adapters/azure-blob-storage.ts"],"sourcesContent":["import type { BlobClient, BlobServiceClient, BlockBlobClient } from \"@azure/storage-blob\";\nimport { Readable } from \"node:stream\";\nimport type streamWeb from \"node:stream/web\";\nimport { StorageAdapterErrors, type StorageAdapter } from \"./_internal/storage.ts\";\n\nexport class AzureBlobStorageService implements StorageAdapter {\n #client: BlobServiceClient;\n\n constructor(client: BlobServiceClient) {\n this.#client = client;\n }\n\n metadata: StorageAdapter[\"metadata\"] = { name: \"Azure Blob Storage\" };\n\n createContainer: StorageAdapter[\"createContainer\"] = async (containerId, options) => {\n try {\n const containerName = genContainerNameFromContainerId(containerId);\n await this.#client.createContainer(containerName, {\n abortSignal: options.abortSignal,\n });\n } catch (error) {\n throw new StorageAdapterErrors.ContainerAlreadyExistsError(containerId, error);\n }\n };\n\n deleteContainer: StorageAdapter[\"deleteContainer\"] = async (containerId, options) => {\n try {\n const containerName = genContainerNameFromContainerId(containerId);\n await this.#client.getContainerClient(containerName).deleteIfExists({\n abortSignal: options.abortSignal,\n });\n } catch (error) {\n throw new StorageAdapterErrors.ContainerDoesNotExistError(containerId, error);\n }\n };\n\n hasContainer: StorageAdapter[\"hasContainer\"] = async (containerId, options) => {\n const containerName = genContainerNameFromContainerId(containerId);\n return await this.#client.getContainerClient(containerName).exists({\n abortSignal: options.abortSignal,\n });\n };\n\n listContainers: StorageAdapter[\"listContainers\"] = async (options) => {\n const containers: string[] = [];\n for await (const item of this.#client.listContainers({\n abortSignal: options.abortSignal,\n })) {\n containers.push(item.name);\n }\n\n return containers;\n };\n\n deleteFiles: StorageAdapter[\"deleteFiles\"] = async (containerId, filePathsOrPrefix, options) => {\n const containerName = genContainerNameFromContainerId(containerId);\n const containerClient = this.#client.getContainerClient(containerName);\n const blobClientsToDelete: BlobClient[] = [];\n\n if (typeof filePathsOrPrefix === \"string\") {\n for await (const blob of containerClient.listBlobsFlat({\n abortSignal: options.abortSignal,\n prefix: filePathsOrPrefix,\n })) {\n blobClientsToDelete.push(containerClient.getBlobClient(blob.name));\n }\n } else {\n for (const filepath of filePathsOrPrefix) {\n blobClientsToDelete.push(containerClient.getBlobClient(filepath));\n }\n }\n\n if (blobClientsToDelete.length === 0) {\n return;\n }\n\n const response = await containerClient.getBlobBatchClient().deleteBlobs(blobClientsToDelete, {\n abortSignal: options.abortSignal,\n });\n\n if (response.errorCode) {\n throw new StorageAdapterErrors.CustomError(\n undefined,\n `Failed to delete ${response.subResponsesFailedCount} blobs in container ${containerId}: ${response.errorCode}`,\n );\n }\n };\n\n uploadFiles: StorageAdapter[\"uploadFiles\"] = async (containerId, files, options) => {\n const containerName = genContainerNameFromContainerId(containerId);\n const containerClient = this.#client.getContainerClient(containerName);\n\n const { errors } = await promisePool(\n files.map(({ content, path, mimeType }) => async (): Promise<void> => {\n await uploadFileToBlobStorage(\n containerClient.getBlockBlobClient(path),\n content,\n mimeType,\n options.abortSignal,\n );\n }),\n 20,\n );\n\n if (errors.length > 0) {\n options.logger.error(`Failed to upload ${errors.length} files. Errors:`, errors);\n }\n };\n\n hasFile: StorageAdapter[\"hasFile\"] = async (containerId, filepath, options) => {\n const containerName = genContainerNameFromContainerId(containerId);\n const containerClient = this.#client.getContainerClient(containerName);\n const blockBlobClient = containerClient.getBlockBlobClient(filepath);\n return await blockBlobClient.exists({ abortSignal: options.abortSignal });\n };\n\n downloadFile: StorageAdapter[\"downloadFile\"] = async (containerId, filepath, options) => {\n const containerName = genContainerNameFromContainerId(containerId);\n const containerClient = this.#client.getContainerClient(containerName);\n const blockBlobClient = containerClient.getBlockBlobClient(filepath);\n\n if (!(await blockBlobClient.exists())) {\n throw new StorageAdapterErrors.FileDoesNotExistError(containerId, filepath);\n }\n\n const downloadResponse = await blockBlobClient.download(0, undefined, {\n abortSignal: options.abortSignal,\n });\n\n if (!downloadResponse.readableStreamBody) {\n throw new StorageAdapterErrors.FileMalformedError(\n containerId,\n filepath,\n \"No readable stream body found.\",\n );\n }\n\n return {\n content: downloadResponse.readableStreamBody as unknown as ReadableStream,\n mimeType: downloadResponse.contentType,\n path: filepath,\n };\n };\n}\n\nfunction genContainerNameFromContainerId(containerId: string): string {\n return containerId\n .replaceAll(/[^\\w-]+/g, \"-\")\n .slice(0, 255)\n .toLowerCase();\n}\n\n// oxlint-disable-next-line max-params\nasync function uploadFileToBlobStorage(\n client: BlockBlobClient,\n data: Blob | string | ReadableStream,\n mimeType: string,\n abortSignal?: AbortSignal,\n): Promise<void> {\n if (typeof data === \"string\") {\n const blob = new Blob([data], { type: mimeType });\n await client.uploadData(blob, {\n abortSignal,\n blobHTTPHeaders: { blobContentType: mimeType },\n });\n return;\n }\n\n if (data instanceof Blob) {\n await client.uploadData(data, {\n abortSignal,\n blobHTTPHeaders: { blobContentType: mimeType },\n });\n return;\n }\n\n if (data instanceof ReadableStream) {\n const stream = data as unknown as streamWeb.ReadableStream;\n await client.uploadStream(Readable.fromWeb(stream), undefined, undefined, {\n abortSignal,\n blobHTTPHeaders: { blobContentType: mimeType },\n });\n return;\n }\n\n throw new Error(`Unknown file type`);\n}\n\nasync function promisePool<Result>(\n tasks: (() => Promise<Result>)[],\n concurrencyLimit: number,\n): Promise<{ errors: unknown[]; results: Result[] }> {\n const promises: Promise<Result>[] = [];\n const errors: unknown[] = [];\n const executing = new Set();\n\n for (const task of tasks) {\n // Start the taskPromise\n const promise = Promise.resolve().then(task);\n promises.push(promise);\n\n // Add to executing set\n executing.add(promise);\n\n // When the promise settles, remove it from executing\n const cleanup = (): boolean => executing.delete(promise);\n promise.then(cleanup).catch((error: unknown) => {\n errors.push(error);\n cleanup();\n });\n\n // If the number of running promises hit concurrencyLimit, wait for one to finish\n if (executing.size >= concurrencyLimit) {\n // oxlint-disable-next-line no-await-in-loop\n await Promise.race(executing);\n }\n }\n\n // Wait for all remaining tasks to finish\n const results = await Promise.all(promises);\n\n return { errors, results };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DatabaseAdapter } from "./~internal/adapter/database.mjs";
|
|
2
|
+
import { CosmosClient } from "@azure/cosmos";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/azure-cosmos-db.d.ts
|
|
5
|
+
declare class AzureCosmosDatabaseService implements DatabaseAdapter {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(client: CosmosClient, dbName?: 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 { AzureCosmosDatabaseService };
|
|
23
|
+
//# sourceMappingURL=azure-cosmos-db.d.mts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DatabaseAdapterErrors } from "./~internal/adapter/database.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/azure-cosmos-db.ts
|
|
4
|
+
var AzureCosmosDatabaseService = class {
|
|
5
|
+
#db;
|
|
6
|
+
constructor(client, dbName = "StoryBooker") {
|
|
7
|
+
this.#db = client.database(dbName);
|
|
8
|
+
}
|
|
9
|
+
metadata = { name: "Azure Cosmos DB" };
|
|
10
|
+
init = async (options) => {
|
|
11
|
+
await this.#db.client.databases.createIfNotExists({ id: this.#db.id }, { abortSignal: options.abortSignal });
|
|
12
|
+
};
|
|
13
|
+
listCollections = async (options) => {
|
|
14
|
+
try {
|
|
15
|
+
return (await this.#db.containers.readAll({ abortSignal: options.abortSignal }).fetchAll()).resources.map((resource) => resource.id);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
throw new DatabaseAdapterErrors.DatabaseNotInitializedError(error);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
createCollection = async (collectionId, options) => {
|
|
21
|
+
try {
|
|
22
|
+
await this.#db.containers.create({ id: collectionId }, { abortSignal: options.abortSignal });
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId, error);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
hasCollection = async (collectionId, options) => {
|
|
28
|
+
try {
|
|
29
|
+
const response = await this.#db.container(collectionId).read({ abortSignal: options.abortSignal });
|
|
30
|
+
return Boolean(response.resource);
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
deleteCollection = async (collectionId, options) => {
|
|
36
|
+
try {
|
|
37
|
+
await this.#db.container(collectionId).delete({ abortSignal: options.abortSignal });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
listDocuments = async (collectionId, _listOptions, options) => {
|
|
43
|
+
return (await this.#db.container(collectionId).items.readAll({ abortSignal: options.abortSignal }).fetchAll()).resources;
|
|
44
|
+
};
|
|
45
|
+
getDocument = async (collectionId, documentId, options) => {
|
|
46
|
+
try {
|
|
47
|
+
const document = (await this.#db.container(collectionId).item(documentId).read({ abortSignal: options.abortSignal })).resource;
|
|
48
|
+
document.id = documentId;
|
|
49
|
+
return document;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
createDocument = async (collectionId, documentData, options) => {
|
|
55
|
+
try {
|
|
56
|
+
await this.#db.container(collectionId).items.create(documentData, { abortSignal: options.abortSignal });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new DatabaseAdapterErrors.DocumentAlreadyExistsError(collectionId, documentData.id, error);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
hasDocument = async (collectionId, documentId, options) => {
|
|
62
|
+
const response = await this.#db.container(collectionId).item(documentId).read({ abortSignal: options.abortSignal });
|
|
63
|
+
return Boolean(response.resource);
|
|
64
|
+
};
|
|
65
|
+
deleteDocument = async (collectionId, documentId, options) => {
|
|
66
|
+
try {
|
|
67
|
+
await this.#db.container(collectionId).item(documentId).delete({ abortSignal: options.abortSignal });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
updateDocument = async (collectionId, documentId, documentData, options) => {
|
|
73
|
+
try {
|
|
74
|
+
await this.#db.container(collectionId).item(documentId).patch({ operations: Object.entries(documentData).map(([key, value]) => ({
|
|
75
|
+
op: "replace",
|
|
76
|
+
path: `/${key}`,
|
|
77
|
+
value
|
|
78
|
+
})) }, { abortSignal: options.abortSignal });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
export { AzureCosmosDatabaseService };
|
|
87
|
+
//# sourceMappingURL=azure-cosmos-db.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-cosmos-db.mjs","names":["#db","document: Document"],"sources":["../src/adapters/azure-cosmos-db.ts"],"sourcesContent":["import type { CosmosClient, Database } from \"@azure/cosmos\";\nimport {\n DatabaseAdapterErrors,\n type DatabaseAdapter,\n type DatabaseAdapterOptions,\n type DatabaseDocumentListOptions,\n type StoryBookerDatabaseDocument,\n} from \"./_internal/database.ts\";\n\nexport class AzureCosmosDatabaseService implements DatabaseAdapter {\n #db: Database;\n\n constructor(client: CosmosClient, dbName = \"StoryBooker\") {\n this.#db = client.database(dbName);\n }\n\n metadata: DatabaseAdapter[\"metadata\"] = { name: \"Azure Cosmos DB\" };\n\n init: DatabaseAdapter[\"init\"] = async (options) => {\n await this.#db.client.databases.createIfNotExists(\n { id: this.#db.id },\n { abortSignal: options.abortSignal },\n );\n };\n\n listCollections: DatabaseAdapter[\"listCollections\"] = async (options) => {\n try {\n const response = await this.#db.containers\n .readAll({ abortSignal: options.abortSignal })\n .fetchAll();\n const collections: string[] = response.resources.map((resource) => resource.id);\n\n return collections;\n } catch (error) {\n throw new DatabaseAdapterErrors.DatabaseNotInitializedError(error);\n }\n };\n\n createCollection: DatabaseAdapter[\"createCollection\"] = async (collectionId, options) => {\n try {\n await this.#db.containers.create({ id: collectionId }, { abortSignal: options.abortSignal });\n } catch (error) {\n throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId, error);\n }\n };\n\n hasCollection: DatabaseAdapter[\"hasCollection\"] = async (collectionId, options) => {\n try {\n const response = await this.#db\n .container(collectionId)\n .read({ abortSignal: options.abortSignal });\n return Boolean(response.resource);\n } catch {\n return false;\n }\n };\n\n deleteCollection: DatabaseAdapter[\"deleteCollection\"] = async (collectionId, options) => {\n try {\n await this.#db.container(collectionId).delete({ abortSignal: options.abortSignal });\n } catch (error) {\n throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);\n }\n };\n\n listDocuments: DatabaseAdapter[\"listDocuments\"] = async <\n Document extends StoryBookerDatabaseDocument,\n >(\n collectionId: string,\n _listOptions: DatabaseDocumentListOptions<Document>,\n options: DatabaseAdapterOptions,\n ) => {\n const items = await this.#db\n .container(collectionId)\n .items.readAll({ abortSignal: options.abortSignal })\n .fetchAll();\n return items.resources as Document[];\n };\n\n getDocument: DatabaseAdapter[\"getDocument\"] = async <\n Document extends StoryBookerDatabaseDocument,\n >(\n collectionId: string,\n documentId: string,\n options: DatabaseAdapterOptions,\n ) => {\n try {\n const item = this.#db.container(collectionId).item(documentId);\n const response = await item.read({ abortSignal: options.abortSignal });\n const document: Document = response.resource;\n document.id = documentId;\n return document;\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n\n createDocument: DatabaseAdapter[\"createDocument\"] = async (\n collectionId,\n documentData,\n options,\n ) => {\n try {\n await this.#db\n .container(collectionId)\n .items.create(documentData, { abortSignal: options.abortSignal });\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 item = this.#db.container(collectionId).item(documentId);\n const response = await item.read({ abortSignal: options.abortSignal });\n return Boolean(response.resource);\n };\n\n deleteDocument: DatabaseAdapter[\"deleteDocument\"] = async (collectionId, documentId, options) => {\n try {\n await this.#db\n .container(collectionId)\n .item(documentId)\n .delete({ abortSignal: options.abortSignal });\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n\n // oxlint-disable-next-line max-params\n updateDocument: DatabaseAdapter[\"updateDocument\"] = async (\n collectionId,\n documentId,\n documentData,\n options,\n ) => {\n try {\n await this.#db\n .container(collectionId)\n .item(documentId)\n .patch<Document>(\n {\n operations: Object.entries(documentData).map(([key, value]) => ({\n op: \"replace\",\n path: `/${key}`,\n value,\n })),\n },\n { abortSignal: options.abortSignal },\n );\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DatabaseAdapter } from "./~internal/adapter/database.mjs";
|
|
2
|
+
import { TableClient, TableServiceClient } from "@azure/data-tables";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/azure-data-tables.d.ts
|
|
5
|
+
type TableClientGenerator = (tableName: string) => TableClient;
|
|
6
|
+
declare class AzureDataTablesDatabaseService implements DatabaseAdapter {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(serviceClient: TableServiceClient, tableClientGenerator: TableClientGenerator);
|
|
9
|
+
metadata: DatabaseAdapter["metadata"];
|
|
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
|
+
hasDocument: DatabaseAdapter["hasDocument"];
|
|
17
|
+
createDocument: DatabaseAdapter["createDocument"];
|
|
18
|
+
deleteDocument: DatabaseAdapter["deleteDocument"];
|
|
19
|
+
updateDocument: DatabaseAdapter["updateDocument"];
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { AzureDataTablesDatabaseService, TableClientGenerator };
|
|
23
|
+
//# sourceMappingURL=azure-data-tables.d.mts.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { DatabaseAdapterErrors } from "./~internal/adapter/database.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/azure-data-tables.ts
|
|
4
|
+
var AzureDataTablesDatabaseService = class {
|
|
5
|
+
#serviceClient;
|
|
6
|
+
#tableClientGenerator;
|
|
7
|
+
constructor(serviceClient, tableClientGenerator) {
|
|
8
|
+
this.#serviceClient = serviceClient;
|
|
9
|
+
this.#tableClientGenerator = tableClientGenerator;
|
|
10
|
+
}
|
|
11
|
+
metadata = { name: "Azure Tables" };
|
|
12
|
+
listCollections = async (options) => {
|
|
13
|
+
const collections = [];
|
|
14
|
+
for await (const table of this.#serviceClient.listTables({ abortSignal: options.abortSignal })) if (table.name) collections.push(table.name);
|
|
15
|
+
return collections;
|
|
16
|
+
};
|
|
17
|
+
createCollection = async (collectionId, options) => {
|
|
18
|
+
try {
|
|
19
|
+
const tableName = genTableNameFromCollectionId(collectionId);
|
|
20
|
+
await this.#serviceClient.createTable(tableName, { abortSignal: options.abortSignal });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId, error);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
hasCollection = async (collectionId, options) => {
|
|
26
|
+
try {
|
|
27
|
+
const tableName = genTableNameFromCollectionId(collectionId);
|
|
28
|
+
const iterator = this.#serviceClient.listTables({
|
|
29
|
+
abortSignal: options.abortSignal,
|
|
30
|
+
queryOptions: { filter: `TableName eq '${tableName}'` }
|
|
31
|
+
});
|
|
32
|
+
for await (const table of iterator) if (table.name === collectionId) return true;
|
|
33
|
+
return false;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
deleteCollection = async (collectionId, options) => {
|
|
39
|
+
try {
|
|
40
|
+
const tableName = genTableNameFromCollectionId(collectionId);
|
|
41
|
+
await this.#serviceClient.deleteTable(tableName, { abortSignal: options.abortSignal });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
listDocuments = async (collectionId, listOptions, options) => {
|
|
47
|
+
const { filter, limit, select, sort } = listOptions ?? {};
|
|
48
|
+
const tableName = genTableNameFromCollectionId(collectionId);
|
|
49
|
+
const pageIterator = this.#tableClientGenerator(tableName).listEntities({
|
|
50
|
+
abortSignal: options.abortSignal,
|
|
51
|
+
queryOptions: {
|
|
52
|
+
filter: typeof filter === "string" ? filter : void 0,
|
|
53
|
+
select
|
|
54
|
+
}
|
|
55
|
+
}).byPage({ maxPageSize: limit });
|
|
56
|
+
const items = [];
|
|
57
|
+
for await (const page of pageIterator) for (const entity of page) {
|
|
58
|
+
const item = entityToItem(entity);
|
|
59
|
+
if (filter && typeof filter === "function") if (filter(item)) items.push(item);
|
|
60
|
+
else continue;
|
|
61
|
+
else items.push(item);
|
|
62
|
+
}
|
|
63
|
+
if (sort && typeof sort === "function") items.sort(sort);
|
|
64
|
+
return items;
|
|
65
|
+
};
|
|
66
|
+
getDocument = async (collectionId, documentId, options) => {
|
|
67
|
+
try {
|
|
68
|
+
const tableName = genTableNameFromCollectionId(collectionId);
|
|
69
|
+
return entityToItem(await this.#tableClientGenerator(tableName).getEntity(collectionId, documentId, { abortSignal: options.abortSignal }));
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
hasDocument = async (collectionId, documentId, options) => {
|
|
75
|
+
try {
|
|
76
|
+
return Boolean(await this.getDocument(collectionId, documentId, options));
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
createDocument = async (collectionId, documentData, options) => {
|
|
82
|
+
try {
|
|
83
|
+
const tableName = genTableNameFromCollectionId(collectionId);
|
|
84
|
+
await this.#tableClientGenerator(tableName).createEntity({
|
|
85
|
+
...documentData,
|
|
86
|
+
partitionKey: collectionId,
|
|
87
|
+
rowKey: documentData.id
|
|
88
|
+
}, { abortSignal: options.abortSignal });
|
|
89
|
+
} catch (error) {
|
|
90
|
+
throw new DatabaseAdapterErrors.DocumentAlreadyExistsError(collectionId, documentData.id, error);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
deleteDocument = async (collectionId, documentId, options) => {
|
|
94
|
+
try {
|
|
95
|
+
const tableName = genTableNameFromCollectionId(collectionId);
|
|
96
|
+
await this.#tableClientGenerator(tableName).deleteEntity(collectionId, documentId, { abortSignal: options.abortSignal });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
updateDocument = async (collectionId, documentId, documentData, options) => {
|
|
102
|
+
try {
|
|
103
|
+
const tableName = genTableNameFromCollectionId(collectionId);
|
|
104
|
+
await this.#tableClientGenerator(tableName).updateEntity({
|
|
105
|
+
...documentData,
|
|
106
|
+
partitionKey: collectionId,
|
|
107
|
+
rowKey: documentId
|
|
108
|
+
}, "Merge", { abortSignal: options.abortSignal });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
function genTableNameFromCollectionId(collectionId) {
|
|
115
|
+
if (/^[A-Za-z][A-Za-z0-9]{2,62}$/.test(collectionId)) return collectionId;
|
|
116
|
+
return collectionId.replaceAll(/\W/g, "").slice(0, 63).padEnd(3, "X");
|
|
117
|
+
}
|
|
118
|
+
function entityToItem(entity) {
|
|
119
|
+
return {
|
|
120
|
+
...entity,
|
|
121
|
+
id: entity.rowKey ?? entity.partitionKey ?? entity.etag
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
export { AzureDataTablesDatabaseService };
|
|
127
|
+
//# sourceMappingURL=azure-data-tables.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-data-tables.mjs","names":["#serviceClient","#tableClientGenerator","collections: string[]","items: Document[]"],"sources":["../src/adapters/azure-data-tables.ts"],"sourcesContent":["import type { TableClient, TableEntityResult, TableServiceClient } from \"@azure/data-tables\";\nimport {\n DatabaseAdapterErrors,\n type DatabaseAdapter,\n type DatabaseAdapterOptions,\n type DatabaseDocumentListOptions,\n type StoryBookerDatabaseDocument,\n} from \"./_internal/database.ts\";\n\nexport type TableClientGenerator = (tableName: string) => TableClient;\n\nexport class AzureDataTablesDatabaseService implements DatabaseAdapter {\n #serviceClient: TableServiceClient;\n #tableClientGenerator: TableClientGenerator;\n\n constructor(serviceClient: TableServiceClient, tableClientGenerator: TableClientGenerator) {\n this.#serviceClient = serviceClient;\n this.#tableClientGenerator = tableClientGenerator;\n }\n\n metadata: DatabaseAdapter[\"metadata\"] = { name: \"Azure Tables\" };\n\n listCollections: DatabaseAdapter[\"listCollections\"] = async (options) => {\n const collections: string[] = [];\n for await (const table of this.#serviceClient.listTables({\n abortSignal: options.abortSignal,\n })) {\n if (table.name) {\n collections.push(table.name);\n }\n }\n\n return collections;\n };\n\n createCollection: DatabaseAdapter[\"createCollection\"] = async (collectionId, options) => {\n try {\n const tableName = genTableNameFromCollectionId(collectionId);\n await this.#serviceClient.createTable(tableName, {\n abortSignal: options.abortSignal,\n });\n } catch (error) {\n throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId, error);\n }\n };\n\n hasCollection: DatabaseAdapter[\"hasCollection\"] = async (collectionId, options) => {\n try {\n const tableName = genTableNameFromCollectionId(collectionId);\n const iterator = this.#serviceClient.listTables({\n abortSignal: options.abortSignal,\n queryOptions: { filter: `TableName eq '${tableName}'` },\n });\n for await (const table of iterator) {\n if (table.name === collectionId) {\n return true;\n }\n }\n\n return false;\n } catch {\n return false;\n }\n };\n\n deleteCollection: DatabaseAdapter[\"deleteCollection\"] = async (collectionId, options) => {\n try {\n const tableName = genTableNameFromCollectionId(collectionId);\n await this.#serviceClient.deleteTable(tableName, {\n abortSignal: options.abortSignal,\n });\n } catch (error) {\n throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);\n }\n };\n\n listDocuments: DatabaseAdapter[\"listDocuments\"] = async <\n Document extends StoryBookerDatabaseDocument,\n >(\n collectionId: string,\n listOptions: DatabaseDocumentListOptions<Document>,\n options: DatabaseAdapterOptions,\n ): Promise<Document[]> => {\n const { filter, limit, select, sort } = listOptions ?? {};\n\n const tableName = genTableNameFromCollectionId(collectionId);\n const tableClient = this.#tableClientGenerator(tableName);\n\n const pageIterator = tableClient\n .listEntities({\n abortSignal: options.abortSignal,\n queryOptions: {\n filter: typeof filter === \"string\" ? filter : undefined,\n select,\n },\n })\n .byPage({ maxPageSize: limit });\n\n const items: Document[] = [];\n for await (const page of pageIterator) {\n for (const entity of page) {\n const item = entityToItem<Document>(entity);\n if (filter && typeof filter === \"function\") {\n if (filter(item)) {\n items.push(item);\n } else {\n continue;\n }\n } else {\n items.push(item);\n }\n }\n }\n\n if (sort && typeof sort === \"function\") {\n items.sort(sort);\n }\n\n return items;\n };\n\n getDocument: DatabaseAdapter[\"getDocument\"] = async <\n Document extends StoryBookerDatabaseDocument,\n >(\n collectionId: string,\n documentId: string,\n options: DatabaseAdapterOptions,\n ): Promise<Document> => {\n try {\n const tableName = genTableNameFromCollectionId(collectionId);\n const tableClient = this.#tableClientGenerator(tableName);\n const entity = await tableClient.getEntity(collectionId, documentId, {\n abortSignal: options.abortSignal,\n });\n\n return entityToItem<Document>(entity);\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n\n hasDocument: DatabaseAdapter[\"hasDocument\"] = async (collectionId, documentId, options) => {\n try {\n return Boolean(await this.getDocument(collectionId, documentId, options));\n } catch {\n return false;\n }\n };\n\n createDocument: DatabaseAdapter[\"createDocument\"] = async (\n collectionId,\n documentData,\n options,\n ) => {\n try {\n const tableName = genTableNameFromCollectionId(collectionId);\n const tableClient = this.#tableClientGenerator(tableName);\n await tableClient.createEntity(\n {\n ...documentData,\n partitionKey: collectionId,\n rowKey: documentData.id,\n },\n { abortSignal: options.abortSignal },\n );\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentAlreadyExistsError(\n collectionId,\n documentData.id,\n error,\n );\n }\n };\n\n deleteDocument: DatabaseAdapter[\"deleteDocument\"] = async (collectionId, documentId, options) => {\n try {\n const tableName = genTableNameFromCollectionId(collectionId);\n const tableClient = this.#tableClientGenerator(tableName);\n await tableClient.deleteEntity(collectionId, documentId, {\n abortSignal: options.abortSignal,\n });\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n\n // oxlint-disable-next-line max-params\n updateDocument: DatabaseAdapter[\"updateDocument\"] = async (\n collectionId,\n documentId,\n documentData,\n options,\n ) => {\n try {\n const tableName = genTableNameFromCollectionId(collectionId);\n const tableClient = this.#tableClientGenerator(tableName);\n await tableClient.updateEntity(\n { ...documentData, partitionKey: collectionId, rowKey: documentId },\n \"Merge\",\n { abortSignal: options.abortSignal },\n );\n } catch (error) {\n throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId, error);\n }\n };\n}\n\nfunction genTableNameFromCollectionId(collectionId: string): string {\n if (/^[A-Za-z][A-Za-z0-9]{2,62}$/.test(collectionId)) {\n return collectionId;\n }\n\n return collectionId.replaceAll(/\\W/g, \"\").slice(0, 63).padEnd(3, \"X\");\n}\n\nfunction entityToItem<Item extends { id: string }>(\n entity: TableEntityResult<Record<string, unknown>>,\n): Item {\n return {\n ...entity,\n id: entity.rowKey ?? entity.partitionKey ?? entity.etag,\n } as unknown as Item;\n}\n"],"mappings":";;;AAWA,IAAa,iCAAb,MAAuE;CACrE;CACA;CAEA,YAAY,eAAmC,sBAA4C;AACzF,QAAKA,gBAAiB;AACtB,QAAKC,uBAAwB;;CAG/B,WAAwC,EAAE,MAAM,gBAAgB;CAEhE,kBAAsD,OAAO,YAAY;EACvE,MAAMC,cAAwB,EAAE;AAChC,aAAW,MAAM,SAAS,MAAKF,cAAe,WAAW,EACvD,aAAa,QAAQ,aACtB,CAAC,CACA,KAAI,MAAM,KACR,aAAY,KAAK,MAAM,KAAK;AAIhC,SAAO;;CAGT,mBAAwD,OAAO,cAAc,YAAY;AACvF,MAAI;GACF,MAAM,YAAY,6BAA6B,aAAa;AAC5D,SAAM,MAAKA,cAAe,YAAY,WAAW,EAC/C,aAAa,QAAQ,aACtB,CAAC;WACK,OAAO;AACd,SAAM,IAAI,sBAAsB,6BAA6B,cAAc,MAAM;;;CAIrF,gBAAkD,OAAO,cAAc,YAAY;AACjF,MAAI;GACF,MAAM,YAAY,6BAA6B,aAAa;GAC5D,MAAM,WAAW,MAAKA,cAAe,WAAW;IAC9C,aAAa,QAAQ;IACrB,cAAc,EAAE,QAAQ,iBAAiB,UAAU,IAAI;IACxD,CAAC;AACF,cAAW,MAAM,SAAS,SACxB,KAAI,MAAM,SAAS,aACjB,QAAO;AAIX,UAAO;UACD;AACN,UAAO;;;CAIX,mBAAwD,OAAO,cAAc,YAAY;AACvF,MAAI;GACF,MAAM,YAAY,6BAA6B,aAAa;AAC5D,SAAM,MAAKA,cAAe,YAAY,WAAW,EAC/C,aAAa,QAAQ,aACtB,CAAC;WACK,OAAO;AACd,SAAM,IAAI,sBAAsB,4BAA4B,cAAc,MAAM;;;CAIpF,gBAAkD,OAGhD,cACA,aACA,YACwB;EACxB,MAAM,EAAE,QAAQ,OAAO,QAAQ,SAAS,eAAe,EAAE;EAEzD,MAAM,YAAY,6BAA6B,aAAa;EAG5D,MAAM,eAFc,MAAKC,qBAAsB,UAAU,CAGtD,aAAa;GACZ,aAAa,QAAQ;GACrB,cAAc;IACZ,QAAQ,OAAO,WAAW,WAAW,SAAS;IAC9C;IACD;GACF,CAAC,CACD,OAAO,EAAE,aAAa,OAAO,CAAC;EAEjC,MAAME,QAAoB,EAAE;AAC5B,aAAW,MAAM,QAAQ,aACvB,MAAK,MAAM,UAAU,MAAM;GACzB,MAAM,OAAO,aAAuB,OAAO;AAC3C,OAAI,UAAU,OAAO,WAAW,WAC9B,KAAI,OAAO,KAAK,CACd,OAAM,KAAK,KAAK;OAEhB;OAGF,OAAM,KAAK,KAAK;;AAKtB,MAAI,QAAQ,OAAO,SAAS,WAC1B,OAAM,KAAK,KAAK;AAGlB,SAAO;;CAGT,cAA8C,OAG5C,cACA,YACA,YACsB;AACtB,MAAI;GACF,MAAM,YAAY,6BAA6B,aAAa;AAM5D,UAAO,aAJQ,MADK,MAAKF,qBAAsB,UAAU,CACxB,UAAU,cAAc,YAAY,EACnE,aAAa,QAAQ,aACtB,CAAC,CAEmC;WAC9B,OAAO;AACd,SAAM,IAAI,sBAAsB,0BAA0B,cAAc,YAAY,MAAM;;;CAI9F,cAA8C,OAAO,cAAc,YAAY,YAAY;AACzF,MAAI;AACF,UAAO,QAAQ,MAAM,KAAK,YAAY,cAAc,YAAY,QAAQ,CAAC;UACnE;AACN,UAAO;;;CAIX,iBAAoD,OAClD,cACA,cACA,YACG;AACH,MAAI;GACF,MAAM,YAAY,6BAA6B,aAAa;AAE5D,SADoB,MAAKA,qBAAsB,UAAU,CACvC,aAChB;IACE,GAAG;IACH,cAAc;IACd,QAAQ,aAAa;IACtB,EACD,EAAE,aAAa,QAAQ,aAAa,CACrC;WACM,OAAO;AACd,SAAM,IAAI,sBAAsB,2BAC9B,cACA,aAAa,IACb,MACD;;;CAIL,iBAAoD,OAAO,cAAc,YAAY,YAAY;AAC/F,MAAI;GACF,MAAM,YAAY,6BAA6B,aAAa;AAE5D,SADoB,MAAKA,qBAAsB,UAAU,CACvC,aAAa,cAAc,YAAY,EACvD,aAAa,QAAQ,aACtB,CAAC;WACK,OAAO;AACd,SAAM,IAAI,sBAAsB,0BAA0B,cAAc,YAAY,MAAM;;;CAK9F,iBAAoD,OAClD,cACA,YACA,cACA,YACG;AACH,MAAI;GACF,MAAM,YAAY,6BAA6B,aAAa;AAE5D,SADoB,MAAKA,qBAAsB,UAAU,CACvC,aAChB;IAAE,GAAG;IAAc,cAAc;IAAc,QAAQ;IAAY,EACnE,SACA,EAAE,aAAa,QAAQ,aAAa,CACrC;WACM,OAAO;AACd,SAAM,IAAI,sBAAsB,0BAA0B,cAAc,YAAY,MAAM;;;;AAKhG,SAAS,6BAA6B,cAA8B;AAClE,KAAI,8BAA8B,KAAK,aAAa,CAClD,QAAO;AAGT,QAAO,aAAa,WAAW,OAAO,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI;;AAGvE,SAAS,aACP,QACM;AACN,QAAO;EACL,GAAG;EACH,IAAI,OAAO,UAAU,OAAO,gBAAgB,OAAO;EACpD"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AuthAdapter, AuthAdapterOptions, StoryBookerPermission, StoryBookerPermissionAction, StoryBookerPermissionKey, StoryBookerPermissionResource, StoryBookerPermissionWithKey, StoryBookerUser } from "./~internal/adapter/auth.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/azure-easy-auth.d.ts
|
|
4
|
+
interface AzureEasyAuthClientPrincipal {
|
|
5
|
+
claims: {
|
|
6
|
+
typ: string;
|
|
7
|
+
val: string;
|
|
8
|
+
}[];
|
|
9
|
+
auth_typ: string;
|
|
10
|
+
name_typ: string;
|
|
11
|
+
role_typ: string;
|
|
12
|
+
}
|
|
13
|
+
interface AzureEasyAuthUser extends StoryBookerUser {
|
|
14
|
+
roles: string[] | null;
|
|
15
|
+
type: "application" | "user";
|
|
16
|
+
clientPrincipal?: AzureEasyAuthClientPrincipal;
|
|
17
|
+
}
|
|
18
|
+
type AuthAdapterAuthorise<AuthUser extends StoryBookerUser = StoryBookerUser> = (permission: StoryBookerPermissionWithKey, user: Omit<AuthUser, "permissions">) => boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Modify the final user details object created from EasyAuth Client Principal.
|
|
21
|
+
*/
|
|
22
|
+
type ModifyUserDetails = <User extends Omit<AzureEasyAuthUser, "permissions">>(user: User, options: AuthAdapterOptions) => User | Promise<User>;
|
|
23
|
+
/**
|
|
24
|
+
* StoryBooker Auth adapter for Azure EasyAuth.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const auth = new AzureEasyAuthService();
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
declare class AzureEasyAuthService implements AuthAdapter<AzureEasyAuthUser> {
|
|
32
|
+
#private;
|
|
33
|
+
metadata: AuthAdapter["metadata"];
|
|
34
|
+
constructor(options?: {
|
|
35
|
+
/**
|
|
36
|
+
* Custom function to authorise permission for user
|
|
37
|
+
*/
|
|
38
|
+
authorise?: AuthAdapterAuthorise<AzureEasyAuthUser>;
|
|
39
|
+
/**
|
|
40
|
+
* Modify the final user details object created from EasyAuth Client Principal.
|
|
41
|
+
*/
|
|
42
|
+
modifyUserDetails?: ModifyUserDetails;
|
|
43
|
+
});
|
|
44
|
+
getUserDetails: AuthAdapter<AzureEasyAuthUser>["getUserDetails"];
|
|
45
|
+
login: AuthAdapter<AzureEasyAuthUser>["login"];
|
|
46
|
+
logout: AuthAdapter<AzureEasyAuthUser>["logout"];
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { AuthAdapterAuthorise, AzureEasyAuthClientPrincipal, AzureEasyAuthService, AzureEasyAuthUser, ModifyUserDetails, type StoryBookerPermission, type StoryBookerPermissionAction, type StoryBookerPermissionKey, type StoryBookerPermissionResource, type StoryBookerPermissionWithKey };
|
|
50
|
+
//# sourceMappingURL=azure-easy-auth.d.mts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { StoryBookerPermissionsAllEnabled, StoryBookerPermissionsList } from "./~internal/adapter/auth.mjs";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/azure-easy-auth.ts
|
|
5
|
+
const DEFAULT_AUTHORISE = (permission, user) => {
|
|
6
|
+
if (!user) return false;
|
|
7
|
+
if (permission.action === "read") return true;
|
|
8
|
+
return Boolean(user.roles && user.roles.length > 0);
|
|
9
|
+
};
|
|
10
|
+
const DEFAULT_MODIFY_USER = (user) => user;
|
|
11
|
+
/**
|
|
12
|
+
* StoryBooker Auth adapter for Azure EasyAuth.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const auth = new AzureEasyAuthService();
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
var AzureEasyAuthService = class {
|
|
20
|
+
#authorise;
|
|
21
|
+
#modifyUserDetails;
|
|
22
|
+
metadata = { name: "Azure Easy Auth" };
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this.#authorise = options?.authorise ?? DEFAULT_AUTHORISE;
|
|
25
|
+
this.#modifyUserDetails = options?.modifyUserDetails ?? DEFAULT_MODIFY_USER;
|
|
26
|
+
}
|
|
27
|
+
getUserDetails = async (options) => {
|
|
28
|
+
const principalHeader = options.request.headers.get("x-ms-client-principal");
|
|
29
|
+
if (!principalHeader) throw new Response(`Unauthorized access. Please provide a valid EasyAuth principal header.`, { status: 401 });
|
|
30
|
+
const decodedPrincipal = Buffer.from(principalHeader, "base64").toString("utf8");
|
|
31
|
+
const clientPrincipal = JSON.parse(decodedPrincipal);
|
|
32
|
+
const claims = clientPrincipal?.claims ?? [];
|
|
33
|
+
const azpToken = claims.find((claim) => claim.typ === "azp")?.val;
|
|
34
|
+
if (azpToken) return {
|
|
35
|
+
clientPrincipal,
|
|
36
|
+
displayName: "App",
|
|
37
|
+
id: azpToken,
|
|
38
|
+
permissions: StoryBookerPermissionsAllEnabled,
|
|
39
|
+
roles: null,
|
|
40
|
+
type: "application"
|
|
41
|
+
};
|
|
42
|
+
const name = claims.find((claim) => claim.typ === "name")?.val;
|
|
43
|
+
const email = claims.find((claim) => claim.typ === clientPrincipal.name_typ)?.val;
|
|
44
|
+
const roles = claims.filter((claim) => claim.typ === clientPrincipal.role_typ || claim.typ === "roles").map((claim) => claim.val);
|
|
45
|
+
const userWithoutPermissions = {
|
|
46
|
+
clientPrincipal,
|
|
47
|
+
displayName: name ?? "",
|
|
48
|
+
id: email ?? "",
|
|
49
|
+
roles,
|
|
50
|
+
title: roles.join(", "),
|
|
51
|
+
type: "user"
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
...await this.#modifyUserDetails(userWithoutPermissions, options),
|
|
55
|
+
permissions: authoriseUserPermissions(this.#authorise, userWithoutPermissions)
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
login = ({ request }) => {
|
|
59
|
+
const url = new URL("/.auth/login", request.url);
|
|
60
|
+
return new Response(null, {
|
|
61
|
+
headers: { Location: url.toString() },
|
|
62
|
+
status: 302
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
logout = (_user, { request }) => {
|
|
66
|
+
const url = new URL("/.auth/logout", request.url);
|
|
67
|
+
return new Response(null, {
|
|
68
|
+
headers: { Location: url.toString() },
|
|
69
|
+
status: 302
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
function authoriseUserPermissions(authorise, user) {
|
|
74
|
+
const permissions = {};
|
|
75
|
+
for (const key of StoryBookerPermissionsList) {
|
|
76
|
+
const [resource, action] = key.split(":");
|
|
77
|
+
permissions[key] = authorise({
|
|
78
|
+
action,
|
|
79
|
+
key,
|
|
80
|
+
resource
|
|
81
|
+
}, user);
|
|
82
|
+
}
|
|
83
|
+
return permissions;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { AzureEasyAuthService };
|
|
88
|
+
//# sourceMappingURL=azure-easy-auth.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-easy-auth.mjs","names":["DEFAULT_AUTHORISE: AuthAdapterAuthorise<AzureEasyAuthUser>","DEFAULT_MODIFY_USER: ModifyUserDetails","#authorise","#modifyUserDetails","clientPrincipal: AzureEasyAuthClientPrincipal","userWithoutPermissions: Omit<AzureEasyAuthUser, \"permissions\">","permissions: AzureEasyAuthUser[\"permissions\"]"],"sources":["../src/adapters/azure-easy-auth.ts"],"sourcesContent":["// oxlint-disable class-methods-use-this\n\nimport { Buffer } from \"node:buffer\";\nimport {\n StoryBookerPermissionsList,\n StoryBookerPermissionsAllEnabled,\n type AuthAdapter,\n type AuthAdapterOptions,\n type StoryBookerPermissionAction,\n type StoryBookerPermissionResource,\n type StoryBookerPermissionWithKey,\n type StoryBookerUser,\n} from \"./_internal/auth.ts\";\n\nexport type {\n StoryBookerPermission,\n StoryBookerPermissionAction,\n StoryBookerPermissionKey,\n StoryBookerPermissionResource,\n StoryBookerPermissionWithKey,\n} from \"./_internal/auth.ts\";\n\nexport interface AzureEasyAuthClientPrincipal {\n claims: { typ: string; val: string }[];\n auth_typ: string;\n name_typ: string;\n role_typ: string;\n}\n\nexport interface AzureEasyAuthUser extends StoryBookerUser {\n roles: string[] | null;\n type: \"application\" | \"user\";\n clientPrincipal?: AzureEasyAuthClientPrincipal;\n}\n\nexport type AuthAdapterAuthorise<AuthUser extends StoryBookerUser = StoryBookerUser> = (\n permission: StoryBookerPermissionWithKey,\n user: Omit<AuthUser, \"permissions\">,\n) => boolean;\n\n/**\n * Modify the final user details object created from EasyAuth Client Principal.\n */\nexport type ModifyUserDetails = <User extends Omit<AzureEasyAuthUser, \"permissions\">>(\n user: User,\n options: AuthAdapterOptions,\n) => User | Promise<User>;\n\nconst DEFAULT_AUTHORISE: AuthAdapterAuthorise<AzureEasyAuthUser> = (permission, user) => {\n if (!user) {\n return false;\n }\n\n if (permission.action === \"read\") {\n return true;\n }\n\n return Boolean(user.roles && user.roles.length > 0);\n};\n\nconst DEFAULT_MODIFY_USER: ModifyUserDetails = (user) => user;\n\n/**\n * StoryBooker Auth adapter for Azure EasyAuth.\n *\n * @example\n * ```ts\n * const auth = new AzureEasyAuthService();\n * ```\n */\nexport class AzureEasyAuthService implements AuthAdapter<AzureEasyAuthUser> {\n #authorise: AuthAdapterAuthorise<AzureEasyAuthUser>;\n #modifyUserDetails: ModifyUserDetails;\n\n metadata: AuthAdapter[\"metadata\"] = { name: \"Azure Easy Auth\" };\n\n constructor(options?: {\n /**\n * Custom function to authorise permission for user\n */\n authorise?: AuthAdapterAuthorise<AzureEasyAuthUser>;\n /**\n * Modify the final user details object created from EasyAuth Client Principal.\n */\n modifyUserDetails?: ModifyUserDetails;\n }) {\n this.#authorise = options?.authorise ?? DEFAULT_AUTHORISE;\n this.#modifyUserDetails = options?.modifyUserDetails ?? DEFAULT_MODIFY_USER;\n }\n\n getUserDetails: AuthAdapter<AzureEasyAuthUser>[\"getUserDetails\"] = async (options) => {\n const principalHeader = options.request.headers.get(\"x-ms-client-principal\");\n if (!principalHeader) {\n throw new Response(`Unauthorized access. Please provide a valid EasyAuth principal header.`, {\n status: 401,\n });\n }\n\n // Decode and parse the claims\n const decodedPrincipal = Buffer.from(principalHeader, \"base64\").toString(\"utf8\");\n\n const clientPrincipal: AzureEasyAuthClientPrincipal = JSON.parse(decodedPrincipal);\n const claims = clientPrincipal?.claims ?? [];\n\n const azpToken = claims.find((claim) => claim.typ === \"azp\")?.val;\n if (azpToken) {\n const user: AzureEasyAuthUser = {\n clientPrincipal,\n displayName: \"App\",\n id: azpToken,\n permissions: StoryBookerPermissionsAllEnabled,\n roles: null,\n type: \"application\",\n };\n return user;\n }\n\n const name = claims.find((claim) => claim.typ === \"name\")?.val;\n const email = claims.find((claim) => claim.typ === clientPrincipal.name_typ)?.val;\n const roles = claims\n .filter((claim) => claim.typ === clientPrincipal.role_typ || claim.typ === \"roles\")\n .map((claim) => claim.val);\n\n const userWithoutPermissions: Omit<AzureEasyAuthUser, \"permissions\"> = {\n clientPrincipal,\n displayName: name ?? \"\",\n id: email ?? \"\",\n roles,\n title: roles.join(\", \"),\n type: \"user\",\n };\n\n return {\n ...(await this.#modifyUserDetails(userWithoutPermissions, options)),\n permissions: authoriseUserPermissions(this.#authorise, userWithoutPermissions),\n };\n };\n\n login: AuthAdapter<AzureEasyAuthUser>[\"login\"] = ({ request }) => {\n const url = new URL(\"/.auth/login\", request.url);\n\n return new Response(null, {\n headers: { Location: url.toString() },\n status: 302,\n });\n };\n\n logout: AuthAdapter<AzureEasyAuthUser>[\"logout\"] = (_user, { request }) => {\n const url = new URL(\"/.auth/logout\", request.url);\n\n return new Response(null, {\n headers: { Location: url.toString() },\n status: 302,\n });\n };\n}\n\nfunction authoriseUserPermissions(\n authorise: AuthAdapterAuthorise<AzureEasyAuthUser>,\n user: Omit<AzureEasyAuthUser, \"permissions\">,\n): AzureEasyAuthUser[\"permissions\"] {\n const permissions: AzureEasyAuthUser[\"permissions\"] = {};\n\n for (const key of StoryBookerPermissionsList) {\n const [resource, action] = key.split(\":\") as [\n StoryBookerPermissionResource,\n StoryBookerPermissionAction,\n ];\n const permission: StoryBookerPermissionWithKey = { action, key, resource };\n permissions[key] = authorise(permission, user);\n }\n\n return permissions;\n}\n"],"mappings":";;;;AAgDA,MAAMA,qBAA8D,YAAY,SAAS;AACvF,KAAI,CAAC,KACH,QAAO;AAGT,KAAI,WAAW,WAAW,OACxB,QAAO;AAGT,QAAO,QAAQ,KAAK,SAAS,KAAK,MAAM,SAAS,EAAE;;AAGrD,MAAMC,uBAA0C,SAAS;;;;;;;;;AAUzD,IAAa,uBAAb,MAA4E;CAC1E;CACA;CAEA,WAAoC,EAAE,MAAM,mBAAmB;CAE/D,YAAY,SAST;AACD,QAAKC,YAAa,SAAS,aAAa;AACxC,QAAKC,oBAAqB,SAAS,qBAAqB;;CAG1D,iBAAmE,OAAO,YAAY;EACpF,MAAM,kBAAkB,QAAQ,QAAQ,QAAQ,IAAI,wBAAwB;AAC5E,MAAI,CAAC,gBACH,OAAM,IAAI,SAAS,0EAA0E,EAC3F,QAAQ,KACT,CAAC;EAIJ,MAAM,mBAAmB,OAAO,KAAK,iBAAiB,SAAS,CAAC,SAAS,OAAO;EAEhF,MAAMC,kBAAgD,KAAK,MAAM,iBAAiB;EAClF,MAAM,SAAS,iBAAiB,UAAU,EAAE;EAE5C,MAAM,WAAW,OAAO,MAAM,UAAU,MAAM,QAAQ,MAAM,EAAE;AAC9D,MAAI,SASF,QARgC;GAC9B;GACA,aAAa;GACb,IAAI;GACJ,aAAa;GACb,OAAO;GACP,MAAM;GACP;EAIH,MAAM,OAAO,OAAO,MAAM,UAAU,MAAM,QAAQ,OAAO,EAAE;EAC3D,MAAM,QAAQ,OAAO,MAAM,UAAU,MAAM,QAAQ,gBAAgB,SAAS,EAAE;EAC9E,MAAM,QAAQ,OACX,QAAQ,UAAU,MAAM,QAAQ,gBAAgB,YAAY,MAAM,QAAQ,QAAQ,CAClF,KAAK,UAAU,MAAM,IAAI;EAE5B,MAAMC,yBAAiE;GACrE;GACA,aAAa,QAAQ;GACrB,IAAI,SAAS;GACb;GACA,OAAO,MAAM,KAAK,KAAK;GACvB,MAAM;GACP;AAED,SAAO;GACL,GAAI,MAAM,MAAKF,kBAAmB,wBAAwB,QAAQ;GAClE,aAAa,yBAAyB,MAAKD,WAAY,uBAAuB;GAC/E;;CAGH,SAAkD,EAAE,cAAc;EAChE,MAAM,MAAM,IAAI,IAAI,gBAAgB,QAAQ,IAAI;AAEhD,SAAO,IAAI,SAAS,MAAM;GACxB,SAAS,EAAE,UAAU,IAAI,UAAU,EAAE;GACrC,QAAQ;GACT,CAAC;;CAGJ,UAAoD,OAAO,EAAE,cAAc;EACzE,MAAM,MAAM,IAAI,IAAI,iBAAiB,QAAQ,IAAI;AAEjD,SAAO,IAAI,SAAS,MAAM;GACxB,SAAS,EAAE,UAAU,IAAI,UAAU,EAAE;GACrC,QAAQ;GACT,CAAC;;;AAIN,SAAS,yBACP,WACA,MACkC;CAClC,MAAMI,cAAgD,EAAE;AAExD,MAAK,MAAM,OAAO,4BAA4B;EAC5C,MAAM,CAAC,UAAU,UAAU,IAAI,MAAM,IAAI;AAKzC,cAAY,OAAO,UAD8B;GAAE;GAAQ;GAAK;GAAU,EACjC,KAAK;;AAGhD,QAAO"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { AuthAdapter, StoryBookerUser } from "./~internal/adapter/auth.mjs";
|
|
2
|
+
import { RouterOptions } from "./~internal/types.mjs";
|
|
3
|
+
import { HttpFunctionOptions, HttpTriggerOptions, SetupOptions, TimerFunctionOptions } from "@azure/functions";
|
|
4
|
+
export * from "storybooker/types";
|
|
5
|
+
|
|
6
|
+
//#region src/adapters/azure-functions.d.ts
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimal representation of Azure Functions App namespace
|
|
10
|
+
* to register HTTP and Timer functions.
|
|
11
|
+
*/
|
|
12
|
+
interface FunctionsApp {
|
|
13
|
+
http(name: string, options: HttpFunctionOptions): void;
|
|
14
|
+
setup?(options: SetupOptions): void;
|
|
15
|
+
timer?(name: string, options: TimerFunctionOptions): void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Options to register the storybooker router
|
|
19
|
+
*/
|
|
20
|
+
interface RegisterStorybookerRouterOptions<User extends StoryBookerUser> extends Omit<RouterOptions<User>, "auth"> {
|
|
21
|
+
/**
|
|
22
|
+
* For authenticating routes, either use an AuthAdapter or Functions auth-level property.
|
|
23
|
+
*
|
|
24
|
+
* - AuthAdapter allows full customization of authentication logic. This will set the function to "anonymous" auth-level.
|
|
25
|
+
* - Auth-level is a simpler way to set predefined authentication levels ("function", "admin").
|
|
26
|
+
* This is a good option to set if the service is used in
|
|
27
|
+
* Headless mode and requires single token authentication for all the requests.
|
|
28
|
+
*
|
|
29
|
+
* @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)
|
|
30
|
+
*/
|
|
31
|
+
auth?: AuthAdapter<User> | Exclude<HttpTriggerOptions["authLevel"], "anonymous">;
|
|
32
|
+
/**
|
|
33
|
+
* Define the route on which all router is placed.
|
|
34
|
+
* Can be a sub-path of the main API route.
|
|
35
|
+
*
|
|
36
|
+
* @default ''
|
|
37
|
+
*/
|
|
38
|
+
route?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Modify the cron-schedule of timer function
|
|
41
|
+
* which purge outdated storybooks.
|
|
42
|
+
*
|
|
43
|
+
* Pass `null` to disable auto-purge functionality.
|
|
44
|
+
*
|
|
45
|
+
* @default "0 0 0 * * *" // Every midnight
|
|
46
|
+
*/
|
|
47
|
+
purgeScheduleCron?: string | null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Register the Storybooker router with the Azure Functions App.
|
|
51
|
+
*
|
|
52
|
+
* - It enabled streaming responses for HTTP functions.
|
|
53
|
+
* - It registers the HTTP function with provided route and auth-level.
|
|
54
|
+
* - It registers the Timer function for purge if `purgeScheduleCron` is not `null`.
|
|
55
|
+
*
|
|
56
|
+
* @param app Azure Functions App instance
|
|
57
|
+
* @param options Options for registering the router
|
|
58
|
+
*/
|
|
59
|
+
declare function registerStoryBookerRouter<User extends StoryBookerUser>(app: FunctionsApp, options: RegisterStorybookerRouterOptions<User>): void;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { RegisterStorybookerRouterOptions, registerStoryBookerRouter };
|
|
62
|
+
//# sourceMappingURL=azure-functions.d.mts.map
|