feat: whyrating - initial project from turbostarter boilerplate

This commit is contained in:
Alejandro Gutiérrez
2026-02-04 01:54:52 +01:00
commit 5cdc07cd39
1618 changed files with 338230 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
import baseConfig from "@turbostarter/eslint-config/base";
export default baseConfig;

View File

@@ -0,0 +1,34 @@
{
"name": "@turbostarter/monitoring-extension",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts",
"./env": "./src/env.ts"
},
"scripts": {
"clean": "git clean -xdf .cache .turbo dist node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"typecheck": "tsc --noEmit"
},
"prettier": "@turbostarter/prettier-config",
"devDependencies": {
"@turbostarter/eslint-config": "workspace:*",
"@turbostarter/prettier-config": "workspace:*",
"@turbostarter/tsconfig": "workspace:*",
"@types/chrome": "^0.0.304",
"eslint": "catalog:",
"prettier": "catalog:",
"typescript": "catalog:"
},
"dependencies": {
"@sentry/browser": "10.30.0",
"@turbostarter/monitoring": "workspace:*",
"@turbostarter/shared": "workspace:*",
"envin": "catalog:",
"posthog-js": "1.283.0",
"uuid": "13.0.0"
}
}

View File

@@ -0,0 +1 @@
export { env, preset } from "./providers";

View File

@@ -0,0 +1 @@
export { captureException, identify, initialize } from "./providers";

View File

@@ -0,0 +1,2 @@
export * from "./posthog";
export * from "./posthog/env";

View File

@@ -0,0 +1,34 @@
/* eslint-disable turbo/no-undeclared-env-vars */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { defineEnv } from "envin";
import * as z from "zod";
import { envConfig } from "@turbostarter/shared/constants";
import type { Preset } from "envin/types";
export const preset = {
id: "posthog",
clientPrefix: "VITE_",
client: {
VITE_POSTHOG_KEY: z.string(),
VITE_POSTHOG_HOST: z
.string()
.optional()
.default("https://us.i.posthog.com"),
},
} as const satisfies Preset;
export const env = defineEnv({
...envConfig,
...preset,
env: {
VITE_POSTHOG_KEY: import.meta.env.VITE_POSTHOG_KEY,
VITE_POSTHOG_HOST: import.meta.env.VITE_POSTHOG_HOST,
},
skip:
(!!import.meta.env.SKIP_ENV_VALIDATION &&
["1", "true"].includes(import.meta.env.SKIP_ENV_VALIDATION)) ||
["lint", "postinstall"].includes(import.meta.env.npm_lifecycle_event),
});

View File

@@ -0,0 +1,62 @@
import { PostHog } from "posthog-js/dist/module.no-external";
import { v7 as uuidv7 } from "uuid";
import { env } from "./env";
import type { MonitoringProviderStrategy } from "@turbostarter/monitoring";
const posthog = new PostHog();
export async function getSharedDistinctId() {
const stored = await chrome.storage.local.get(["posthog_distinct_id"]);
if (stored.posthog_distinct_id) {
return stored.posthog_distinct_id as string;
}
const distinctId = uuidv7();
await chrome.storage.local.set({ posthog_distinct_id: distinctId });
return distinctId;
}
const init = async () => {
if (posthog.__loaded) {
return;
}
const distinctID = await getSharedDistinctId();
posthog.init(env.VITE_POSTHOG_KEY, {
bootstrap: {
distinctID,
},
api_host: env.VITE_POSTHOG_HOST,
disable_external_dependency_loading: true,
error_tracking: {
captureExtensionExceptions: true,
},
persistence: "localStorage",
});
};
export const { captureException, identify, initialize } = {
captureException: (exception) => {
void (async () => {
await init();
posthog.captureException(exception);
})();
},
identify: <T extends { id: string }>(user: T | null) => {
void (async () => {
await init();
if (user) {
posthog.identify(user.id);
} else {
posthog.reset();
}
})();
},
initialize: () => {
void (async () => {
await init();
})();
},
} satisfies MonitoringProviderStrategy;

View File

@@ -0,0 +1,33 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable turbo/no-undeclared-env-vars */
import { defineEnv } from "envin";
import * as z from "zod";
import { envConfig, NodeEnv } from "@turbostarter/shared/constants";
import type { Preset } from "envin/types";
export const preset = {
id: "sentry",
clientPrefix: "VITE_",
client: {
VITE_SENTRY_DSN: z.string(),
VITE_SENTRY_ENVIRONMENT: z
.string()
.default(process.env.NODE_ENV ?? NodeEnv.DEVELOPMENT),
},
} as const satisfies Preset;
export const env = defineEnv({
...envConfig,
...preset,
env: {
VITE_SENTRY_DSN: import.meta.env.VITE_SENTRY_DSN,
VITE_SENTRY_ENVIRONMENT: import.meta.env.VITE_SENTRY_ENVIRONMENT,
},
skip:
(!!import.meta.env.SKIP_ENV_VALIDATION &&
["1", "true"].includes(import.meta.env.SKIP_ENV_VALIDATION)) ||
["lint", "postinstall"].includes(import.meta.env.npm_lifecycle_event),
});

View File

@@ -0,0 +1,45 @@
import * as Sentry from "@sentry/browser";
import { env } from "./env";
import type { MonitoringProviderStrategy } from "@turbostarter/monitoring";
export const { captureException, identify, initialize } = {
captureException: (exception) => {
Sentry.captureException(exception);
},
identify: (user: Sentry.User | null) => {
Sentry.setUser(user);
},
initialize: () => {
const environment = env.VITE_SENTRY_ENVIRONMENT;
Sentry.init({
dsn: env.VITE_SENTRY_DSN,
environment,
// Replay may only be enabled for the client-side
integrations: [
// add your desired integrations here
// https://docs.sentry.io/platforms/javascript/configuration/integrations/
],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
// Adds more context data to events (IP address, cookies, user, etc.)
// For more information, visit: https://docs.sentry.io/platforms/react-native/data-management/data-collected/
sendDefaultPii: true,
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps,
});
},
} satisfies MonitoringProviderStrategy;

View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,6 @@
{
"extends": "@turbostarter/tsconfig/internal.json",
"compilerOptions": {},
"include": ["*.ts", "src/**/*"],
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,3 @@
import baseConfig from "@turbostarter/eslint-config/base";
export default baseConfig;

View File

@@ -0,0 +1,32 @@
{
"name": "@turbostarter/monitoring-mobile",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts",
"./env": "./src/env.ts"
},
"scripts": {
"clean": "git clean -xdf .cache .turbo dist node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"typecheck": "tsc --noEmit"
},
"prettier": "@turbostarter/prettier-config",
"devDependencies": {
"@turbostarter/eslint-config": "workspace:*",
"@turbostarter/prettier-config": "workspace:*",
"@turbostarter/tsconfig": "workspace:*",
"eslint": "catalog:",
"prettier": "catalog:",
"typescript": "catalog:"
},
"dependencies": {
"@sentry/react-native": "7.7.0",
"@turbostarter/monitoring": "workspace:*",
"@turbostarter/shared": "workspace:*",
"envin": "catalog:",
"posthog-react-native": "4.14.3"
}
}

View File

@@ -0,0 +1 @@
export { env, preset } from "./providers";

View File

@@ -0,0 +1 @@
export { captureException, identify, initialize } from "./providers";

View File

@@ -0,0 +1,2 @@
export * from "./posthog";
export * from "./posthog/env";

View File

@@ -0,0 +1,29 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { defineEnv } from "envin";
import * as z from "zod";
import { envConfig } from "@turbostarter/shared/constants";
import type { Preset } from "envin/types";
export const preset = {
id: "posthog",
clientPrefix: "EXPO_PUBLIC_",
client: {
EXPO_PUBLIC_POSTHOG_KEY: z.string(),
EXPO_PUBLIC_POSTHOG_HOST: z
.string()
.optional()
.default("https://us.i.posthog.com"),
},
} as const satisfies Preset;
export const env = defineEnv({
...envConfig,
...preset,
env: {
...process.env,
EXPO_PUBLIC_POSTHOG_KEY: process.env.EXPO_PUBLIC_POSTHOG_KEY,
EXPO_PUBLIC_POSTHOG_HOST: process.env.EXPO_PUBLIC_POSTHOG_HOST,
},
});

View File

@@ -0,0 +1,39 @@
import { PostHog } from "posthog-react-native";
import { env } from "./env";
import type { MonitoringProviderStrategy } from "@turbostarter/monitoring";
let client: PostHog | null = null;
const getClient = () => {
client ??= new PostHog(env.EXPO_PUBLIC_POSTHOG_KEY, {
host: env.EXPO_PUBLIC_POSTHOG_HOST,
errorTracking: {
autocapture: {
uncaughtExceptions: true,
unhandledRejections: true,
console: ["error", "warn"],
},
},
});
return client;
};
export const { captureException, identify, initialize } = {
captureException: (exception) => {
const posthog = getClient();
posthog.captureException(exception);
},
identify: <T extends { id: string }>(user: T | null) => {
const posthog = getClient();
if (user) {
posthog.identify(user.id);
} else {
posthog.reset();
}
},
initialize: () => {
/* PostHog is initialized in the app */
},
} satisfies MonitoringProviderStrategy;

View File

@@ -0,0 +1,28 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { defineEnv } from "envin";
import * as z from "zod";
import { envConfig, NodeEnv } from "@turbostarter/shared/constants";
import type { Preset } from "envin/types";
export const preset = {
id: "sentry",
clientPrefix: "EXPO_PUBLIC_",
client: {
EXPO_PUBLIC_SENTRY_DSN: z.string(),
EXPO_PUBLIC_SENTRY_ENVIRONMENT: z
.string()
.default(process.env.APP_ENV ?? NodeEnv.DEVELOPMENT),
},
} as const satisfies Preset;
export const env = defineEnv({
...envConfig,
...preset,
env: {
...process.env,
EXPO_PUBLIC_SENTRY_DSN: process.env.EXPO_PUBLIC_SENTRY_DSN,
EXPO_PUBLIC_SENTRY_ENVIRONMENT: process.env.EXPO_PUBLIC_SENTRY_ENVIRONMENT,
},
});

View File

@@ -0,0 +1,45 @@
import * as Sentry from "@sentry/react-native";
import { env } from "./env";
import type { MonitoringProviderStrategy } from "@turbostarter/monitoring";
export const { captureException, identify, initialize } = {
captureException: (exception) => {
Sentry.captureException(exception);
},
identify: (user: Sentry.User | null) => {
Sentry.setUser(user);
},
initialize: () => {
const environment = env.EXPO_PUBLIC_SENTRY_ENVIRONMENT;
Sentry.init({
dsn: env.EXPO_PUBLIC_SENTRY_DSN,
environment,
// Replay may only be enabled for the client-side
integrations: [
// add your desired integrations here
// https://docs.sentry.io/platforms/javascript/configuration/integrations/
],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
// Adds more context data to events (IP address, cookies, user, etc.)
// For more information, visit: https://docs.sentry.io/platforms/react-native/data-management/data-collected/
sendDefaultPii: true,
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps,
});
},
} satisfies MonitoringProviderStrategy;

View File

@@ -0,0 +1,6 @@
{
"extends": "@turbostarter/tsconfig/internal.json",
"compilerOptions": {},
"include": ["*.ts", "src/**/*"],
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,3 @@
import baseConfig from "@turbostarter/eslint-config/base";
export default baseConfig;

View File

@@ -0,0 +1,24 @@
{
"name": "@turbostarter/monitoring",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"clean": "git clean -xdf .cache .turbo dist node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"typecheck": "tsc --noEmit"
},
"prettier": "@turbostarter/prettier-config",
"devDependencies": {
"@turbostarter/eslint-config": "workspace:*",
"@turbostarter/prettier-config": "workspace:*",
"@turbostarter/tsconfig": "workspace:*",
"eslint": "catalog:",
"prettier": "catalog:",
"typescript": "catalog:"
}
}

View File

@@ -0,0 +1 @@
export * from "./types";

View File

@@ -0,0 +1,21 @@
export interface MonitoringProviderStrategy {
/**
* Capture an exception
* @param error - The error to capture
*/
captureException<Extra extends Record<string, unknown>>(
error: unknown,
extra?: Extra,
): void;
/**
* Identify a user in the monitoring service - used for tracking user actions
* @param info
*/
identify<Info extends { id: string }>(info: Info): unknown;
/**
* Initialize the monitoring service
*/
initialize(): void | Promise<void>;
}

View File

@@ -0,0 +1,6 @@
{
"extends": "@turbostarter/tsconfig/internal.json",
"compilerOptions": {},
"include": ["*.ts", "src/**/*"],
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,3 @@
import baseConfig from "@turbostarter/eslint-config/base";
export default baseConfig;

View File

@@ -0,0 +1,34 @@
{
"name": "@turbostarter/monitoring-web",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts",
"./env": "./src/env.ts",
"./server": "./src/server.ts"
},
"scripts": {
"clean": "git clean -xdf .cache .turbo dist node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"typecheck": "tsc --noEmit"
},
"prettier": "@turbostarter/prettier-config",
"devDependencies": {
"@turbostarter/eslint-config": "workspace:*",
"@turbostarter/prettier-config": "workspace:*",
"@turbostarter/tsconfig": "workspace:*",
"eslint": "catalog:",
"prettier": "catalog:",
"typescript": "catalog:"
},
"dependencies": {
"@sentry/nextjs": "10.30.0",
"@turbostarter/monitoring": "workspace:*",
"@turbostarter/shared": "workspace:*",
"envin": "catalog:",
"posthog-js": "1.283.0",
"posthog-node": "5.11.0"
}
}

View File

@@ -0,0 +1 @@
export * from "./providers/env";

View File

@@ -0,0 +1 @@
export * from "./providers";

View File

@@ -0,0 +1 @@
export * from "./posthog/env";

View File

@@ -0,0 +1 @@
export * from "./posthog";

View File

@@ -0,0 +1,29 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { defineEnv } from "envin";
import * as z from "zod";
import { envConfig } from "@turbostarter/shared/constants";
import type { Preset } from "envin/types";
export const preset = {
id: "posthog",
clientPrefix: "NEXT_PUBLIC_",
client: {
NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(),
NEXT_PUBLIC_POSTHOG_HOST: z
.string()
.optional()
.default("https://us.i.posthog.com"),
},
} as const satisfies Preset;
export const env = defineEnv({
...envConfig,
...preset,
env: {
...process.env,
NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
},
});

View File

@@ -0,0 +1,44 @@
import posthog from "posthog-js";
import { env } from "./env";
import type { MonitoringProviderClientStrategy } from "../types";
const isValidPosthogConfig =
env.NEXT_PUBLIC_POSTHOG_KEY &&
env.NEXT_PUBLIC_POSTHOG_KEY !== "notyet" &&
env.NEXT_PUBLIC_POSTHOG_HOST.startsWith("http");
let initialized = false;
export const {
captureException,
identify,
initialize,
onRouterTransitionStart,
} = {
captureException: (exception) => {
if (!initialized) return;
posthog.captureException(exception);
},
identify: <T extends { id: string }>(user: T | null) => {
if (!initialized) return;
if (user) {
posthog.identify(user.id);
} else {
posthog.reset();
}
},
initialize: () => {
if (!isValidPosthogConfig) return;
const key = env.NEXT_PUBLIC_POSTHOG_KEY;
if (!key) return;
posthog.init(key, {
api_host: env.NEXT_PUBLIC_POSTHOG_HOST,
});
initialized = true;
},
onRouterTransitionStart: () => {
/* PostHog does not provide a way to capture router transitions yet */
},
} satisfies MonitoringProviderClientStrategy;

View File

@@ -0,0 +1,81 @@
import { PostHog } from "posthog-node";
import { env } from "./env";
import type { MonitoringProviderServerStrategy } from "../types";
const isValidPosthogConfig =
env.NEXT_PUBLIC_POSTHOG_KEY &&
env.NEXT_PUBLIC_POSTHOG_KEY !== "notyet" &&
env.NEXT_PUBLIC_POSTHOG_HOST.startsWith("http");
let posthogInstance: PostHog | null = null;
export function getPostHogServer() {
if (!isValidPosthogConfig) {
return null;
}
const key = env.NEXT_PUBLIC_POSTHOG_KEY;
if (!key) return null;
posthogInstance ??= new PostHog(key, {
host: env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1,
flushInterval: 0,
});
return posthogInstance;
}
export const { captureException, initialize, onRequestError } = {
captureException: (exception, extra = undefined) => {
const posthog = getPostHogServer();
if (!posthog) return;
const distinctId = typeof extra?.id === "string" ? extra.id : undefined;
posthog.captureException(exception, distinctId, extra);
},
initialize: () => {
getPostHogServer();
},
onRequestError: (error, request) => {
/* eslint-disable-next-line turbo/no-undeclared-env-vars, no-restricted-properties */
if (process.env.NEXT_RUNTIME !== "nodejs") {
return;
}
const posthog = getPostHogServer();
if (!posthog) return;
let distinctId: string | undefined;
if (request.headers.cookie) {
const cookieString = Array.isArray(request.headers.cookie)
? request.headers.cookie.join("; ")
: request.headers.cookie;
const postHogCookieMatch = /ph_phc_.*?_posthog=([^;]+)/.exec(
cookieString,
);
if (postHogCookieMatch?.[1]) {
try {
const decodedCookie = decodeURIComponent(postHogCookieMatch[1]);
const data: unknown = JSON.parse(decodedCookie);
if (
typeof data === "object" &&
data !== null &&
"distinct_id" in data &&
typeof data.distinct_id === "string"
) {
distinctId = data.distinct_id;
}
} catch {
/* Ignore */
}
}
}
posthog.captureException(error, distinctId);
},
} satisfies MonitoringProviderServerStrategy;

View File

@@ -0,0 +1,26 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { defineEnv } from "envin";
import * as z from "zod";
import { envConfig } from "@turbostarter/shared/constants";
import type { Preset } from "envin/types";
export const preset = {
id: "sentry",
clientPrefix: "NEXT_PUBLIC_",
client: {
NEXT_PUBLIC_SENTRY_DSN: z.string().optional().default(""),
NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().default(process.env.NODE_ENV),
},
} as const satisfies Preset;
export const env = defineEnv({
...envConfig,
...preset,
env: {
...process.env,
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
},
});

View File

@@ -0,0 +1,51 @@
import * as Sentry from "@sentry/nextjs";
import { env } from "./env";
import type { MonitoringProviderClientStrategy } from "../types";
export const {
captureException,
identify,
initialize,
onRouterTransitionStart,
} = {
captureException: (exception) => {
Sentry.captureException(exception);
},
identify: (user: Sentry.User | null) => {
Sentry.setUser(user);
},
initialize: () => {
const environment = env.NEXT_PUBLIC_SENTRY_ENVIRONMENT;
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_DSN,
environment,
// Replay may only be enabled for the client-side
integrations: [
// add your desired integrations here
// https://docs.sentry.io/platforms/javascript/configuration/integrations/
],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
// Adds more context data to events (IP address, cookies, user, etc.)
// For more information, visit: https://docs.sentry.io/platforms/react-native/data-management/data-collected/
sendDefaultPii: true,
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps,
});
},
onRouterTransitionStart: Sentry.captureRouterTransitionStart,
} satisfies MonitoringProviderClientStrategy;

View File

@@ -0,0 +1,28 @@
import * as Sentry from "@sentry/nextjs";
import { env } from "./env";
import type { MonitoringProviderServerStrategy } from "../types";
export const { captureException, initialize, onRequestError } = {
captureException: (exception) => {
Sentry.captureException(exception);
},
initialize: () => {
const environment = env.NEXT_PUBLIC_SENTRY_ENVIRONMENT;
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_DSN,
environment,
// Adds more context data to events (IP address, cookies, user, etc.)
// For more information, visit: https://docs.sentry.io/platforms/react-native/data-management/data-collected/
sendDefaultPii: true,
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps,
});
},
onRequestError: Sentry.captureRequestError,
} satisfies MonitoringProviderServerStrategy;

View File

@@ -0,0 +1 @@
export * from "./posthog/server";

View File

@@ -0,0 +1,26 @@
import type { MonitoringProviderStrategy } from "@turbostarter/monitoring";
export interface MonitoringProviderClientStrategy
extends MonitoringProviderStrategy {
onRouterTransitionStart: (
href: string,
navigationType: string,
) => void | Promise<void>;
}
export interface MonitoringProviderServerStrategy
extends Omit<MonitoringProviderStrategy, "identify"> {
onRequestError: (
error: unknown,
errorRequest: Readonly<{
path: string;
method: string;
headers: NodeJS.Dict<string | string[]>;
}>,
errorContext: Readonly<{
routerKind: string;
routePath: string;
routeType: string;
}>,
) => void | Promise<void>;
}

View File

@@ -0,0 +1 @@
export * from "./providers/server";

View File

@@ -0,0 +1,8 @@
{
"extends": "@turbostarter/tsconfig/internal.json",
"compilerOptions": {
"lib": ["dom"]
},
"include": ["*.ts", "src/**/*"],
"exclude": ["node_modules"]
}