diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..49a420b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +jobs: + test: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + fail-fast: false + + steps: + - id: checkout + name: Checkout + uses: actions/checkout@v3 + - id: setup-bun + name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - id: install-deps + name: Install dependencies + run: | + bun install + - id: test + name: Run test + run: | + bun test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..216ba3b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v3 + with: + node-version: 16.x + + - run: npx changelogithub + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/LICENSE b/LICENSE index 6b37b3c..53c76fc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Moe Ayman +Copyright (c) 2022 Robert Soriano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 22b8499..a52c4a0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,26 @@ -# firebase-admin-rest - Firebase admin wrapper on REST apis works on Vercel edge functions, cloudflare workers, bun, deno or any js runtime. +# bun starter + +## Getting Started + +Click the [Use this template](https://github.com/wobsoriano/bun-lib-starter/generate) button to create a new repository with the contents starter. + +OR + +Run `bun create wobsoriano/bun-lib-starter ./my-lib`. + +## Setup + +```bash +# install dependencies +bun install + +# test the app +bun test + +# build the app, available under dist +bun run build +``` + +## License + +MIT diff --git a/build.mjs b/build.mjs new file mode 100644 index 0000000..dd6ef5f --- /dev/null +++ b/build.mjs @@ -0,0 +1,8 @@ +import dts from 'bun-plugin-dts' + +await Bun.build({ + entrypoints: ['./src/index.ts'], + outdir: './dist', + minify: true, + plugins: [dts()] +}) diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..f9c71d7 Binary files /dev/null and b/bun.lockb differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..313bc78 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "firebase-edge", + "version": "0.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "description": "", + "scripts": { + "build": "bun run build.mjs", + "prepublishOnly": "bun run build", + "dev-test": "bun run src/tests/index.ts" + }, + "files": [ + "dist" + ], + "keywords": [ + "bun" + ], + "license": "MIT", + "homepage": "https://github.com/wobsoriano/pkg-name#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/wobsoriano/pkg-name.git" + }, + "bugs": "https://github.com/wobsoriano/pkg-name/issues", + "author": "Robert Soriano ", + "devDependencies": { + "bun-plugin-dts": "^0.2.1", + "@types/bun": "^1.0.0", + "typescript": "^5.2.2" + }, + "dependencies": { + "@tsndr/cloudflare-worker-jwt": "^2.5.3", + "dotenv": "^16.4.5" + } +} diff --git a/src/firebase-auth-utils/generateJWT.ts b/src/firebase-auth-utils/generateJWT.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/firebase-auth-utils/index.ts b/src/firebase-auth-utils/index.ts new file mode 100644 index 0000000..693da49 --- /dev/null +++ b/src/firebase-auth-utils/index.ts @@ -0,0 +1 @@ +export {} \ No newline at end of file diff --git a/src/firebase-auth-utils/initFirebase.ts b/src/firebase-auth-utils/initFirebase.ts new file mode 100644 index 0000000..252b8a0 --- /dev/null +++ b/src/firebase-auth-utils/initFirebase.ts @@ -0,0 +1,58 @@ + + +import * as jwt from '@tsndr/cloudflare-worker-jwt' +import { FirebaseAdminConfig, FirestoreDatabase } from '../types'; + +export async function generateJWT(input?: { + service?: string, + serviceAccount?: { + client_email: string; + private_key: string; + private_key_id: string; + } +}) { + const serviceAccount = input?.serviceAccount || JSON.parse(process.env.GCLOUD_SERVICE_ACCOUNT || '{}'); + // const serviceAccount: { + // client_email: string; + // private_key: string; + // private_key_id: string; + // } | undefined = JSON.parse(process.env.SERVICE_ACCOUNT || '{}'); + if (!serviceAccount) throw new Error('SERVICE_ACCOUNT not found in environment variables'); + const privateKey = serviceAccount.private_key; + const signedJwt = await jwt.sign( + { + iss: serviceAccount.client_email, + sub: serviceAccount.client_email, + // scope: 'https://www.googleapis.com/auth/datastore', + aud: `https://${input?.service || 'firestore'}.googleapis.com/`, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 3600, // Expires in 1 hour + }, + privateKey, + { + algorithm: 'RS256', + header: { + 'kid': serviceAccount.private_key_id, + 'typ': 'JWT', + 'alg': 'RS256', + }, + }); + + return signedJwt; +} + +export async function initEdgeFirebase(options: { + serviceAccount: FirebaseAdminConfig + databaseId?: string; +}): Promise { + if (options.serviceAccount.project_id === undefined) throw new Error('project_id is required in serviceAccount.'); + const accessToken = await generateJWT({ serviceAccount: options.serviceAccount }); + process.env.FIREBASE_REST_ACCESS_TOKEN = accessToken; + process.env.FIREBASE_REST_PROJECT_ID = options.serviceAccount.project_id; + process.env.FIREBASE_REST_DATABASE_ID = options.databaseId || '(default)'; + return { + name: options.databaseId || '(default)', + projectId: options.serviceAccount?.project_id, + accessToken: accessToken + }; +} \ No newline at end of file diff --git a/src/firestore/deleteDoc.ts b/src/firestore/deleteDoc.ts new file mode 100644 index 0000000..36fec92 --- /dev/null +++ b/src/firestore/deleteDoc.ts @@ -0,0 +1,47 @@ +import { typedEnv } from ".."; +import { generateFirebaseReqHeaders } from "./utils"; + +/** + * Fetches documents from Firestore. + * @param {string} collectionPath - The path to the Firestore collection. + * @param {Object} options - Additional options for fetching documents. + * @param {number | undefined} options.limit - Optional. The maximum number of documents to fetch defaults to 100. + * @param {string | undefined} options.nextPageToken - The token for fetching the next page of documents. + * @template T - The type of the documents being fetched. Defaults to 'any'. +* @returns {Promise>} A Promise that resolves to a response object containing fetched Firestore documents. + */ +export async function deleteDocEdge( + docPath: string, + options?: { + limit?: number, + nextPageToken?: string + }): Promise<{ + response?: Response, + error?: any, + }> { + + try { + let qs = new URLSearchParams({ + fields: 'documents(fields,name),nextPageToken', + }); + + if (options?.nextPageToken) { + qs.append('pageToken', options.nextPageToken); + } + + const deleteResponse = await fetch(`https://firestore.googleapis.com/v1beta1/projects/speakwiz-app/databases/${typedEnv.FIREBASE_REST_DATABASE_ID}/documents/${docPath}`, { + method: 'DELETE', + headers: { + ...generateFirebaseReqHeaders() + }, + }) + return { + response: deleteResponse + } + } catch (error) { + return { + error: error + } + } + +} \ No newline at end of file diff --git a/src/firestore/getDoc.ts b/src/firestore/getDoc.ts new file mode 100644 index 0000000..ab84cf7 --- /dev/null +++ b/src/firestore/getDoc.ts @@ -0,0 +1,48 @@ +import { typedEnv } from ".."; +import { CompatibleDocument } from "../types"; +import { formatValuesWithType, generateFirebaseReqHeaders } from "./utils"; + +/** + * Fetches documents from Firestore. + * @param {string} docPath - The path to the Firestore document. + * @param {Object | undefined} options - Additional options for fetching documents. + * @param {number | undefined} options.db - Optional. database-id instead of the default one. + * @template T - The type of the document being fetched. Defaults to 'any'. +* @returns {Promise>} A Promise that resolves to a response object containing fetched Firestore document. + */ +export async function getDocEdge( + docPath: string, + options?: { + db?: string + }): Promise { + try { + const response: any = await fetch(`https://firestore.googleapis.com/v1beta1/projects/speakwiz-app/databases/${typedEnv.FIREBASE_REST_PROJECT_ID}/documents/${docPath}`, { + method: 'GET', + headers: generateFirebaseReqHeaders(options?.db || typedEnv.FIREBASE_REST_DATABASE_ID) + } + ).then((res) => res.json()); + // console.log(response) + + if (response?.fields) { + return { + id: docPath.includes(`/`) ? (docPath.split('/').pop() || docPath) : docPath, + exists: () => true, + data: () => formatValuesWithType(response) + } + } else { + return { + id: docPath.includes(`/`) ? (docPath.split('/').pop() || docPath) : docPath, + exists: () => false, + data: () => undefined, + } + } + } catch (error) { + console.error(error) + return { + id: docPath.includes(`/`) ? (docPath.split('/').pop() || docPath) : docPath, + exists: () => false, + data: () => undefined, + error: error, + } + } +} diff --git a/src/firestore/getDocs.ts b/src/firestore/getDocs.ts new file mode 100644 index 0000000..1894888 --- /dev/null +++ b/src/firestore/getDocs.ts @@ -0,0 +1,73 @@ + +import { typedEnv } from ".."; +import { FirestoreDocument, RestDocuments } from "../types"; +import { formatValuesWithType, generateFirebaseReqHeaders } from "./utils"; + +/** + * Fetches documents from Firestore. + * @param {string} collectionPath - The path to the Firestore collection. + * @param {Object| undefined} options - Additional options for fetching documents. + * @param {number | undefined} options.limit - Optional. The maximum number of documents to fetch defaults to 100. + * @param {string | undefined} options.nextPageToken - The token for fetching the next page of documents. + * @template T - The type of the documents being fetched. Defaults to 'any'. +* @returns {Promise>} A Promise that resolves to a response object containing fetched Firestore documents. + */ +export async function getDocsEdge( + collectionPath: string, + options?: { + limit?: number, + nextPageToken?: string + }): Promise> { + try { + + let qs = new URLSearchParams({ + fields: 'documents(fields,name),nextPageToken', + }); + + if (options?.nextPageToken) { + qs.append('pageToken', options.nextPageToken); + } + + const res: any = await fetch(`https://firestore.googleapis.com/v1beta1/projects/speakwiz-app/databases/${typedEnv.FIREBASE_REST_DATABASE_ID}/documents/${collectionPath}?${qs.toString()}&pageSize=${options?.limit || 100}`, { + method: 'GET', + headers: { + ...generateFirebaseReqHeaders(typedEnv.FIREBASE_REST_DATABASE_ID) + // "Authorization": "Bearer " + accessToken, + // "x-goog-request-params": `project_id=speakwiz-app&database_id=${options.bucket}` + }, + }).then((res) => res.json()); + + const rawDocs = res?.documents || []; + + if (rawDocs?.length > 0) { + const docs = rawDocs.map((docRef: { + name: string, + fields: { + [key: string]: FirestoreDocument + } + }) => { + return formatValuesWithType(docRef); + }); + + return { + size: docs?.length, + empty: docs?.length === 0, + docs: docs + }; + } else { + return { + size: 0, + empty: true, + docs: [] + }; + } + } catch (error) { + console.error(error); + return { + size: 0, + empty: true, + docs: [], + error: error + }; + } +} diff --git a/src/firestore/index.ts b/src/firestore/index.ts new file mode 100644 index 0000000..a50f291 --- /dev/null +++ b/src/firestore/index.ts @@ -0,0 +1,11 @@ +import { initEdgeFirebase } from "../firebase-auth-utils/initFirebase"; +import { deleteDocEdge } from "./deleteDoc"; +import { getDocEdge } from "./getDoc"; +import { getDocsEdge } from "./getDocs"; + +export { + initEdgeFirebase, + getDocEdge, + getDocsEdge, + deleteDocEdge +} \ No newline at end of file diff --git a/src/firestore/queryDocs.ts b/src/firestore/queryDocs.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/firestore/setDoc.ts b/src/firestore/setDoc.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/firestore/updateDoc.ts b/src/firestore/updateDoc.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/firestore/utils.ts b/src/firestore/utils.ts new file mode 100644 index 0000000..edbf2af --- /dev/null +++ b/src/firestore/utils.ts @@ -0,0 +1,118 @@ +import { typedEnv } from ".."; +import { FirestoreDocument } from "../types"; + +export function humanValueToDumbGoogle(value: any): FirestoreDocument { + switch (typeof value) { + case 'string': + return { stringValue: value }; + case 'number': + if ( + Number.isInteger(value) + ) { + return { integerValue: value }; + } + return { doubleValue: value }; + case 'boolean': + return { booleanValue: value }; + case 'object': + if (Array.isArray(value)) { + return { arrayValue: { values: value.map((v) => humanValueToDumbGoogle(v)) } } + } + return { mapValue: { fields: Object.keys(value).reduce((acc, k) => ({ ...acc, [k]: humanValueToDumbGoogle(value[k]) }), {}) } } + case null: + return { nullValue: null }; + default: + return value; + } +} + +export function humanObjectToDumbGoogle(inputObject: { + [key: string]: any +}) { + let fields: { + [key: string]: FirestoreDocument + } = {}; + for (const key in inputObject) { + if (inputObject.hasOwnProperty(key)) { + const value = inputObject[key]; + fields[key] = humanValueToDumbGoogle(value); + } + } + return fields; +} + +export function googleDumbObjectToHuman(inputObject: { + [key: string]: FirestoreDocument +}) { + let fields: { + [key: string]: any + } = {}; + for (const key in inputObject) { + if (inputObject.hasOwnProperty(key)) { + const value = inputObject[key]; + // console.log(`map`, value) + if (value['arrayValue']) { + fields[key] = value.arrayValue?.values?.map((value) => { + if (value['mapValue']) { + return googleDumbObjectToHuman(value.mapValue.fields) + } else { + return googleDumbValueToHuman(value) + } + }) + } else if (value['mapValue']) { + fields[key] = googleDumbObjectToHuman(value?.mapValue?.fields || {}); + } + else { + fields[key] = googleDumbValueToHuman(value); + } + } + } + return fields; +} + +export function googleDumbValueToHuman(inputObject: { + [key: string]: any +}) { + for (const key in inputObject) { + if (inputObject.hasOwnProperty(key)) { + const value = inputObject[key]; + switch (key) { + case 'stringValue': + return value; + case 'integerValue': + return Number(value); + case 'booleanValue': + return value; + case "nullValue": + return null; + default: + return value; + } + } + } +} + +export function formatValuesWithType(responseFB: { + name: string, + fields: { + [key: string]: FirestoreDocument + } +}) { + // console.log(inputObject) + // console.log(`responseFB: `, responseFB) + const id = responseFB?.name ? responseFB?.name?.split('/').pop() : undefined; + const inputObject = responseFB?.fields; + try { + return { ...googleDumbObjectToHuman(inputObject), id: id }; + } catch (error) { + console.log(`error in formatValuesWithType: `, error) + return {} + } +} + +export function generateFirebaseReqHeaders(db?: string){ + return { + "Authorization": "Bearer " + typedEnv.FIREBASE_REST_ACCESS_TOKEN, + "x-goog-request-params": `project_id=${typedEnv.FIREBASE_REST_PROJECT_ID}&database_id=${typedEnv.FIREBASE_REST_DATABASE_ID || db || "(default)"}` + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1f7c2dd --- /dev/null +++ b/src/index.ts @@ -0,0 +1,10 @@ +export const typedEnv = { ...process.env } as { + FIREBASE_REST_SERVICE_ACCOUNT: string; + FIREBASE_REST_ACCESS_TOKEN: string; + FIREBASE_REST_PROJECT_ID: string; + FIREBASE_REST_DATABASE_ID: string; + [key: string]: string; +} + +export * from './firebase-auth-utils' +export * from './firestore'; diff --git a/src/templates/tsdoc_sample.ts b/src/templates/tsdoc_sample.ts new file mode 100644 index 0000000..d56b594 --- /dev/null +++ b/src/templates/tsdoc_sample.ts @@ -0,0 +1,23 @@ +/** + * Initializes Firebase for Edge deployment. + * @param path - The path to the document. + * @param options - The initialization options. + * @param options.serviceAccount - The service account credentials. + * @param options.serviceAccount.projectId - The Firebase project ID. + * @param options.serviceAccount.privateKey - The private key for authentication. + * @param options.serviceAccount.clientEmail - The client email for authentication. + * @param options.databaseId - (Optional) The ID of the Firebase database. + * @returns Promise + */ +export async function FUNCTION_NAME( + path: string, + options: { + serviceAccount: { + projectId: string; + privateKey: string; + clientEmail: string; + }; + databaseId?: string; + }) { + +} diff --git a/src/tests/index.ts b/src/tests/index.ts new file mode 100644 index 0000000..60632dd --- /dev/null +++ b/src/tests/index.ts @@ -0,0 +1,21 @@ +import { initEdgeFirebase } from "../firestore"; + +export const testAdminConfig = { + "type": "service_account", + "project_id": "speakwiz-app", + "private_key_id": "2e83211104d89eb0312536a06381e2dc5b209d1c", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDDTmmhi4J8hRCZ\nTN7LtlHhaDyBV1niKYPmIU3ekqqjq8hj3nEFw9CmlS1cSr9mTRdi16sQbdm+AaM1\ntZfvPh27Ft2V5qUMDIiAomUU8kWJGpoj27r2ZTX96ePFhFbStsNJwH2sLRZ5/jrl\nmni8mE5FISshkHnnXGz1qEGLymmhiF8S3iq03/yZ/9SChSKvI51XG/ayjMALc61J\nJN1+k0IcLSythF3RlNF5NxlU5vCEWhdC2jhbZ4ZXyeoVoN+LRuk7M/CZmy2dDVNk\nr/DwjgfSgOZFAoNqJiKF/ryI6cD79RvZSHS+RlRxZeGC2hwOV11w9eoa07pLHvl0\nr6B2N2FzAgMBAAECggEATpQ968E1vuPKyeMjwNKaHxbRQTjj8RrC9tRvgB2CileT\nSJs23hrq2BstJPTuDTr3Lc2YBgQsl0YRZIqrlpZnX97TSHyD61UflqHACa1wTLln\nSwYdMwWFs6NnARE93YmrCQFpjtyVLoAbMkX0Tez5kNbHg7mdUnjdXflUZeoKlfKT\nj5lUvi3xLlKPcPH5SE3Cz7fN5vDb/2iGBEh7SwiynwjAWhxrUOCv2fhax73swzFX\nWRVU7oPJF+Z0/Df02CnlVnS15//Na1Y4IJt2grb0GES3WaAeTB1wCaplph8gaCgc\n45eqQy1Cucaw+ofR8YZADAGJMRtV1BNDENKkWlCwAQKBgQD15RQM0l7D9fXp5kcv\neWgKv3a33MceIFUqG+UG+hWEgd0SmQVrTWXQmWMOhbGghMqQ+zVY2HVq5aQA8vsg\n9ijLtL+P4UyuZrFPzDezPmX6G5ruYL0z+LbACqWlvOlKv0c0IRtx3ah52gBTw6HQ\ncivqPI4O0/KYVQsgBBYT5LKXmwKBgQDLVR7j3sAj3IvNX1XF5/6STmCbvY0Pbi6d\ndc7BM6z8mH5mKzTz8M4Qdc6PKHGyKYVTOillsBCioicHPssryQNr0CS5S6suo19l\n3AH1U0S5ruPz3nNejDHoUNbD/ugqXEkyKz0INYOzERd3/M3r/D7aLFAKAizfA/hI\n7yV1w1V3CQKBgQClJPAdWGBa/eLl70mZ4dD1fveNrpJwckigWlGsKOOwtcMzDWBt\nW3Lo8Uts4m+Unfqp+n0uqVnarFZEaOwujASEI2WQjUEB1Gh7bm1uTZcRrd6VAJWx\nxPV/7uandEO+ds6sfRvAkpznEXmsyDPyGevSik7iOIiytFMfcn8dZzhmxwKBgErs\nea5zxQ8x1F3/1CZRvy+AK/8XUKQv8INbBq2Qchy9wE27fA6rW/Maxdtghsykmhk5\n5EkxIGAdKg50Z/8hWd5fWzjgFhrgXmW1NQ+F+FwHgr246YAcXsOBDjI4eqopSVtw\nLVQaDAZutNwkzmg3kZ1pGLEnbgtbdiDB5mbHbHWpAoGBAOG82JoscKQdaz9ilsiK\niHIkZ/YnHvesqATyLKFLU+tG/2hgJqCftTbm9fdlrDFK0aOaqQlmfGslPkDNOMqi\npHZ0sMitDOpakSCRtd6tAHymuHZFNQPsJjNFKDI+Nw+J1sIHHkzvmVcNDwpAjst6\n2Q/uKBAy6MkeSxrTuvChEoEQ\n-----END PRIVATE KEY-----\n", + "client_email": "vg-rest@speakwiz-app.iam.gserviceaccount.com", + "client_id": "103588609938596427689", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/vg-rest%40speakwiz-app.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} + +initEdgeFirebase({ + serviceAccount: testAdminConfig +}).then((res) => { + console.log(`access token: ${process.env.GCLOUD_ACCESS_TOKEN}`) +}) \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..adb6fd9 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,98 @@ +/** + * Firebase Admin Configuration interface. + */ +export interface FirebaseAdminConfig { + /** + * The type of the service account. + */ + type?: string; + /** + * The project ID associated with the service account. + */ + project_id: string; + /** + * The private key ID of the service account. + */ + private_key_id: string; + /** + * The private key of the service account. + */ + private_key: string; + /** + * The client email of the service account. + */ + client_email: string; + /** + * The client ID of the service account. + */ + client_id?: string; + /** + * The authentication URI for the service account. + */ + auth_uri?: string; + /** + * The token URI for the service account. + */ + token_uri?: string; + /** + * The authentication provider's x509 certificate URL. + */ + auth_provider_x509_cert_url?: string; + /** + * The client's x509 certificate URL. + */ + client_x509_cert_url?: string; + /** + * The universe domain associated with the service account. + */ + universe_domain?: string; +} + +export interface FirestoreDatabase { + name: string, + projectId?: string, + accessToken: string, +} + + +export interface FirestoreDocument { + stringValue?: string, + integerValue?: number, + doubleValue?: number, + booleanValue?: boolean, + arrayValue?: { values: any[] }, + mapValue?: { fields: any }, + nullValue?: null +} + +/** + * Represents a RESTful response object containing a document. + * @template T - The type of document in the response. Defaults to 'any'. + * @param exists - The number of documents in the response. + * @param data - The document data of type T. + * @param error - Optional. Represents any error information in the response. + */ +export interface CompatibleDocument { + id: string, + exists: () => boolean + data?: () => T, + error?: any +} + +/** + * Represents a RESTful response object containing documents. + * @template T - The type of documents in the response. Defaults to 'any'. + * @param size - The number of documents in the response. + * @param empty - Indicates whether the response is empty. + * @param docs - An array of documents of type T. + * @param error - Optional. Represents any error information in the response. + */ +export interface RestDocuments { + size: number; + empty: boolean; + docs: T[]; + /** + * Optional. Represents any error information in the response. + */ + error?: any; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b7cd75b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "esnext", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "skipLibCheck": true, + "noUnusedLocals": true, + "noImplicitAny": true, + "allowJs": true, + "noEmit": true, + "outDir": "dist", + "resolveJsonModule": true + } +}