mirror of
https://github.com/papra-hq/papra.git
synced 2025-12-19 12:19:37 -06:00
Compare commits
9 Commits
@papra/app
...
@papra/app
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2efe7321cd | ||
|
|
947bdf8385 | ||
|
|
b5bf0cca4b | ||
|
|
208a561668 | ||
|
|
40cb1d71d5 | ||
|
|
3da13f7591 | ||
|
|
2a444aad31 | ||
|
|
47d8bbd356 | ||
|
|
ed4d7e4a00 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -35,6 +35,8 @@ cache
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
*.sqlite
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
|
||||
local-documents
|
||||
ingestion
|
||||
|
||||
@@ -105,6 +105,73 @@ We recommend running the app locally for development. Follow these steps:
|
||||
|
||||
6. Open your browser and navigate to `http://localhost:3000`.
|
||||
|
||||
### IDE Setup
|
||||
|
||||
#### ESLint Extension
|
||||
|
||||
We recommend installing the [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) for VS Code to get real-time linting feedback and automatic code fixing.
|
||||
The linting configuration is based on [@antfu/eslint-config](https://github.com/antfu/eslint-config), you can find specific IDE configurations in their repository.
|
||||
|
||||
<details>
|
||||
<summary>Recommended VS Code Settings</summary>
|
||||
|
||||
Create or update your `.vscode/settings.json` file with the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
// Disable the default formatter, use eslint instead
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
|
||||
// Silent the stylistic rules in your IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "style/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "format/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-indent", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-spacing", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-spaces", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-order", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-dangle", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-newline", "severity": "off", "fixable": true },
|
||||
{ "rule": "*quotes", "severity": "off", "fixable": true },
|
||||
{ "rule": "*semi", "severity": "off", "fixable": true }
|
||||
],
|
||||
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml",
|
||||
"toml",
|
||||
"xml",
|
||||
"gql",
|
||||
"graphql",
|
||||
"astro",
|
||||
"svelte",
|
||||
"css",
|
||||
"less",
|
||||
"scss",
|
||||
"pcss",
|
||||
"postcss"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Testing
|
||||
|
||||
We use **Vitest** for testing. Each package comes with its own testing commands.
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @papra/app-client
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#501](https://github.com/papra-hq/papra/pull/501) [`b5bf0cc`](https://github.com/papra-hq/papra/commit/b5bf0cca4b571495329cb553da06e0d334ee8968) Thanks [@CorentinTh](https://github.com/CorentinTh)! - Fix an issue preventing to disable the max upload size
|
||||
|
||||
- [#498](https://github.com/papra-hq/papra/pull/498) [`3da13f7`](https://github.com/papra-hq/papra/commit/3da13f759155df5d7c532160a7ea582385db63b6) Thanks [@CorentinTh](https://github.com/CorentinTh)! - Removed the "open in new tab" button for security improvement (xss prevention)
|
||||
|
||||
## 0.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@papra/app-client",
|
||||
"type": "module",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.12.3",
|
||||
"description": "Papra frontend client",
|
||||
@@ -21,12 +21,10 @@
|
||||
"serve": "vite preview",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"test": "pnpm check-i18n-types-outdated && vitest run",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch",
|
||||
"test:e2e": "playwright test",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"check-i18n-types-outdated": "pnpm script:generate-i18n-types && git diff --exit-code -- src/modules/i18n/locales.types.ts > /dev/null || (echo \"Locales types are outdated, please run 'pnpm script:generate-i18n-types' and commit the changes.\" && exit 1)",
|
||||
"script:get-missing-i18n-keys": "tsx src/scripts/get-missing-i18n-keys.script.ts",
|
||||
"script:sync-i18n-key-order": "tsx src/scripts/sync-i18n-key-order.script.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -72,9 +72,10 @@ export const DocumentUploadProvider: ParentComponent = (props) => {
|
||||
setState('open');
|
||||
|
||||
await Promise.all(files.map(async (file) => {
|
||||
const { maxUploadSize } = config.documentsStorage;
|
||||
updateTaskStatus({ file, status: 'uploading' });
|
||||
|
||||
if (file.size > config.documentsStorage.maxUploadSize) {
|
||||
if (maxUploadSize > 0 && file.size > maxUploadSize) {
|
||||
updateTaskStatus({ file, status: 'error', error: Object.assign(new Error('File too large'), { code: 'document.size_too_large' }) });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -214,15 +214,6 @@ export const DocumentPage: Component = () => {
|
||||
{t('documents.actions.download')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => window.open(getDataUrl()!, '_blank')}
|
||||
size="sm"
|
||||
>
|
||||
<div class="i-tabler-eye size-4 mr-2"></div>
|
||||
{t('documents.actions.open-in-new-tab')}
|
||||
</Button>
|
||||
|
||||
{getDocument().isDeleted
|
||||
? (
|
||||
<Button
|
||||
|
||||
9
apps/papra-client/vitest.config.ts
Normal file
9
apps/papra-client/vitest.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
env: {
|
||||
TZ: 'UTC',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,15 @@
|
||||
# @papra/app-server
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#493](https://github.com/papra-hq/papra/pull/493) [`ed4d7e4`](https://github.com/papra-hq/papra/commit/ed4d7e4a00b2ca2c7fe808201c322f957d6ed990) Thanks [@CorentinTh](https://github.com/CorentinTh)! - Fix to allow cross docker volume file moving when consumption is done
|
||||
|
||||
- [#500](https://github.com/papra-hq/papra/pull/500) [`208a561`](https://github.com/papra-hq/papra/commit/208a561668ed2d1019430a9f4f5c5d3fd4cde603) Thanks [@CorentinTh](https://github.com/CorentinTh)! - Added the possibility to define a Libsql/Sqlite driver for the tasks service
|
||||
|
||||
- [#499](https://github.com/papra-hq/papra/pull/499) [`40cb1d7`](https://github.com/papra-hq/papra/commit/40cb1d71d5e52c40aab7ea2c6bc222cea6d55b70) Thanks [@CorentinTh](https://github.com/CorentinTh)! - Enhanced security by serving files as attachement and with an octet-stream content type
|
||||
|
||||
## 0.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@papra/app-server",
|
||||
"type": "module",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.12.3",
|
||||
"description": "Papra app server",
|
||||
@@ -42,6 +42,7 @@
|
||||
"@aws-sdk/lib-storage": "^3.835.0",
|
||||
"@azure/storage-blob": "^12.27.0",
|
||||
"@cadence-mq/core": "^0.2.1",
|
||||
"@cadence-mq/driver-libsql": "^0.2.1",
|
||||
"@cadence-mq/driver-memory": "^0.2.0",
|
||||
"@corentinth/chisels": "^1.3.1",
|
||||
"@corentinth/friendly-ids": "^0.0.1",
|
||||
|
||||
@@ -21,6 +21,8 @@ const { db, client } = setupDatabase(config.database);
|
||||
const documentsStorageService = createDocumentStorageService({ documentStorageConfig: config.documentsStorage });
|
||||
|
||||
const taskServices = createTaskServices({ config });
|
||||
await taskServices.initialize();
|
||||
|
||||
const { app } = await createServer({ config, db, taskServices, documentsStorageService });
|
||||
|
||||
const server = serve(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Context, RouteDefinitionContext } from '../server.types';
|
||||
import type { Session } from './auth.types';
|
||||
import { get } from 'lodash-es';
|
||||
import { isDefined } from '../../shared/utils';
|
||||
import { isDefined, isString } from '../../shared/utils';
|
||||
|
||||
export function registerAuthRoutes({ app, auth, config }: RouteDefinitionContext) {
|
||||
app.on(
|
||||
@@ -26,7 +26,7 @@ export function registerAuthRoutes({ app, auth, config }: RouteDefinitionContext
|
||||
app.use('*', async (context: Context, next) => {
|
||||
const overrideUserId: unknown = get(context.env, 'loggedInUserId');
|
||||
|
||||
if (isDefined(overrideUserId) && typeof overrideUserId === 'string') {
|
||||
if (isDefined(overrideUserId) && isString(overrideUserId)) {
|
||||
context.set('userId', overrideUserId);
|
||||
context.set('session', {} as Session);
|
||||
context.set('authType', 'session');
|
||||
|
||||
@@ -15,6 +15,7 @@ import { intakeEmailsConfig } from '../intake-emails/intake-emails.config';
|
||||
import { organizationsConfig } from '../organizations/organizations.config';
|
||||
import { organizationPlansConfig } from '../plans/plans.config';
|
||||
import { createLogger } from '../shared/logger/logger';
|
||||
import { isString } from '../shared/utils';
|
||||
import { subscriptionsConfig } from '../subscriptions/subscriptions.config';
|
||||
import { tasksConfig } from '../tasks/tasks.config';
|
||||
import { trackingConfig } from '../tracking/tracking.config';
|
||||
@@ -71,7 +72,7 @@ export const configDefinition = {
|
||||
schema: z.union([
|
||||
z.string(),
|
||||
z.array(z.string()),
|
||||
]).transform(value => (typeof value === 'string' ? value.split(',') : value)),
|
||||
]).transform(value => (isString(value) ? value.split(',') : value)),
|
||||
default: ['http://localhost:3000'],
|
||||
env: 'SERVER_CORS_ORIGINS',
|
||||
},
|
||||
|
||||
@@ -288,9 +288,13 @@ function setupGetDocumentFileRoute({ app, db, documentsStorageService }: RouteDe
|
||||
Readable.toWeb(fileStream),
|
||||
200,
|
||||
{
|
||||
'Content-Type': document.mimeType,
|
||||
'Content-Disposition': `inline; filename*=UTF-8''${encodeURIComponent(document.name)}`,
|
||||
// Prevent XSS by serving the file as an octet-stream
|
||||
'Content-Type': 'application/octet-stream',
|
||||
// Always use attachment for defense in depth - client uses blob API anyway
|
||||
'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(document.name)}`,
|
||||
'Content-Length': String(document.originalSize),
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'X-Frame-Options': 'DENY',
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, S3Client } fr
|
||||
|
||||
import { Upload } from '@aws-sdk/lib-storage';
|
||||
import { safely } from '@corentinth/chisels';
|
||||
import { isString } from '../../../../shared/utils';
|
||||
import { createFileNotFoundError } from '../../document-storage.errors';
|
||||
import { defineStorageDriver } from '../drivers.models';
|
||||
|
||||
@@ -12,7 +13,7 @@ function isS3NotFoundError(error: Error) {
|
||||
const codes = ['NoSuchKey', 'NotFound'];
|
||||
|
||||
return codes.includes(error.name)
|
||||
|| ('Code' in error && typeof error.Code === 'string' && codes.includes(error.Code));
|
||||
|| ('Code' in error && isString(error.Code) && codes.includes(error.Code));
|
||||
}
|
||||
|
||||
export const s3StorageDriverFactory = defineStorageDriver(({ documentStorageConfig }) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ConfigDefinition } from 'figue';
|
||||
import { z } from 'zod';
|
||||
import { booleanishSchema } from '../config/config.schemas';
|
||||
import { isString } from '../shared/utils';
|
||||
import { defaultIgnoredPatterns } from './ingestion-folders.constants';
|
||||
|
||||
export const ingestionFolderConfig = {
|
||||
@@ -61,7 +62,7 @@ export const ingestionFolderConfig = {
|
||||
schema: z.union([
|
||||
z.string(),
|
||||
z.array(z.string()),
|
||||
]).transform(value => (typeof value === 'string' ? value.split(',') : value)),
|
||||
]).transform(value => (isString(value) ? value.split(',') : value)),
|
||||
default: defaultIgnoredPatterns,
|
||||
env: 'INGESTION_FOLDER_IGNORED_PATTERNS',
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { FsNative } from './fs.services';
|
||||
import { memfs } from 'memfs';
|
||||
import { createFsServices } from './fs.services';
|
||||
|
||||
export function createInMemoryFsServices(volume: NestedDirectoryJSON) {
|
||||
export function buildInMemoryFs(volume: NestedDirectoryJSON) {
|
||||
const { vol } = memfs(volume);
|
||||
|
||||
const fs = {
|
||||
@@ -12,7 +12,16 @@ export function createInMemoryFsServices(volume: NestedDirectoryJSON) {
|
||||
} as FsNative;
|
||||
|
||||
return {
|
||||
fs,
|
||||
getFsState: () => vol.toJSON(),
|
||||
};
|
||||
}
|
||||
|
||||
export function createInMemoryFsServices(volume: NestedDirectoryJSON) {
|
||||
const { fs, getFsState } = buildInMemoryFs(volume);
|
||||
|
||||
return {
|
||||
getFsState,
|
||||
fs: createFsServices({ fs }),
|
||||
};
|
||||
}
|
||||
|
||||
12
apps/papra-server/src/modules/shared/fs/fs.models.ts
Normal file
12
apps/papra-server/src/modules/shared/fs/fs.models.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { isNil, isString } from '../utils';
|
||||
|
||||
export function isCrossDeviceError({ error }: { error: Error & { code?: unknown } }) {
|
||||
if (isNil(error.code) || !isString(error.code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'EXDEV', // Linux based OS (see `man rename`)
|
||||
'ERROR_NOT_SAME_DEVICE', // Windows
|
||||
].includes(error.code);
|
||||
}
|
||||
45
apps/papra-server/src/modules/shared/fs/fs.services.test.ts
Normal file
45
apps/papra-server/src/modules/shared/fs/fs.services.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { buildInMemoryFs } from './fs.in-memory';
|
||||
import { moveFile } from './fs.services';
|
||||
|
||||
describe('fs services', () => {
|
||||
describe('moveFile', () => {
|
||||
test('moves a file from the source path to the destination path', async () => {
|
||||
const { fs, getFsState } = buildInMemoryFs({
|
||||
'/file.txt': 'test content',
|
||||
});
|
||||
|
||||
await moveFile({
|
||||
sourceFilePath: '/file.txt',
|
||||
destinationFilePath: '/renamed.txt',
|
||||
fs,
|
||||
});
|
||||
|
||||
expect(getFsState()).to.eql({
|
||||
'/renamed.txt': 'test content',
|
||||
});
|
||||
});
|
||||
|
||||
test('if the destination file is in a different partition or disk, or a different docker volume, the underlying rename operation fails with an EXDEV error, so we fallback to copy + delete the source file', async () => {
|
||||
const { fs, getFsState } = buildInMemoryFs({
|
||||
'/file.txt': 'test content',
|
||||
});
|
||||
|
||||
await moveFile({
|
||||
sourceFilePath: '/file.txt',
|
||||
destinationFilePath: '/renamed.txt',
|
||||
fs: {
|
||||
...fs,
|
||||
rename: async () => {
|
||||
// Simulate an EXDEV error
|
||||
throw Object.assign(new Error('EXDEV'), { code: 'EXDEV' });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(getFsState()).to.eql({
|
||||
'/renamed.txt': 'test content',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,9 @@ import type { Readable } from 'node:stream';
|
||||
import { Buffer } from 'node:buffer';
|
||||
import fsSyncNative from 'node:fs';
|
||||
import fsPromisesNative from 'node:fs/promises';
|
||||
import { injectArguments } from '@corentinth/chisels';
|
||||
import { injectArguments, safely } from '@corentinth/chisels';
|
||||
import { pick } from 'lodash-es';
|
||||
import { isCrossDeviceError } from './fs.models';
|
||||
|
||||
// what we use from the native fs module
|
||||
export type FsNative = {
|
||||
@@ -13,12 +14,13 @@ export type FsNative = {
|
||||
stat: (path: string) => Promise<{ size: number }>;
|
||||
readFile: (path: string) => Promise<Buffer>;
|
||||
access: (path: string, mode: number) => Promise<void>;
|
||||
copyFile: (sourcePath: string, destinationPath: string) => Promise<void>;
|
||||
constants: { F_OK: number };
|
||||
createReadStream: (path: string) => Readable;
|
||||
};
|
||||
|
||||
const fsNative = {
|
||||
...pick(fsPromisesNative, 'mkdir', 'unlink', 'rename', 'readFile', 'access', 'constants', 'stat'),
|
||||
...pick(fsPromisesNative, 'mkdir', 'unlink', 'rename', 'readFile', 'access', 'constants', 'stat', 'copyFile'),
|
||||
createReadStream: fsSyncNative.createReadStream.bind(fsSyncNative) as (filePath: string) => Readable,
|
||||
} as FsNative;
|
||||
|
||||
@@ -66,7 +68,19 @@ export async function deleteFile({ filePath, fs = fsNative }: { filePath: string
|
||||
}
|
||||
|
||||
export async function moveFile({ sourceFilePath, destinationFilePath, fs = fsNative }: { sourceFilePath: string; destinationFilePath: string; fs?: FsNative }) {
|
||||
await fs.rename(sourceFilePath, destinationFilePath);
|
||||
const [, error] = await safely(fs.rename(sourceFilePath, destinationFilePath));
|
||||
|
||||
// With different docker volumes, the rename operation fails with an EXDEV error,
|
||||
// so we fallback to copy and delete the source file
|
||||
if (error && isCrossDeviceError({ error })) {
|
||||
await fs.copyFile(sourceFilePath, destinationFilePath);
|
||||
await fs.unlink(sourceFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function readFile({ filePath, fs = fsNative }: { filePath: string; fs?: FsNative }) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { isDefined, isNil, omitUndefined } from './utils';
|
||||
import { isDefined, isNil, isNonEmptyString, isString, omitUndefined } from './utils';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('omitUndefined', () => {
|
||||
@@ -47,4 +47,38 @@ describe('utils', () => {
|
||||
expect(isDefined({})).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isString', () => {
|
||||
test('returns true if the value is a string', () => {
|
||||
expect(isString('')).toBe(true);
|
||||
expect(isString('foo')).toBe(true);
|
||||
expect(isString(String(1))).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false if the value is not a string', () => {
|
||||
expect(isString(undefined)).toBe(false);
|
||||
expect(isString(null)).toBe(false);
|
||||
expect(isString(0)).toBe(false);
|
||||
expect(isString(false)).toBe(false);
|
||||
expect(isString({})).toBe(false);
|
||||
expect(isString([])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNonEmptyString', () => {
|
||||
test('returns true if the value is a non-empty string', () => {
|
||||
expect(isNonEmptyString('')).toBe(false);
|
||||
expect(isNonEmptyString('foo')).toBe(true);
|
||||
expect(isNonEmptyString(String(1))).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false if the value is not a non-empty string', () => {
|
||||
expect(isNonEmptyString(undefined)).toBe(false);
|
||||
expect(isNonEmptyString(null)).toBe(false);
|
||||
expect(isNonEmptyString(0)).toBe(false);
|
||||
expect(isNonEmptyString(false)).toBe(false);
|
||||
expect(isNonEmptyString({})).toBe(false);
|
||||
expect(isNonEmptyString([])).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,3 +15,11 @@ export function isNil(value: unknown): value is undefined | null {
|
||||
export function isDefined<T>(value: T): value is Exclude<T, undefined | null> {
|
||||
return !isNil(value);
|
||||
}
|
||||
|
||||
export function isString(value: unknown): value is string {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
export function isNonEmptyString(value: unknown): value is string {
|
||||
return isString(value) && value.length > 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isNil } from '../shared/utils';
|
||||
import { isNil, isNonEmptyString } from '../shared/utils';
|
||||
|
||||
export function coerceStripeTimestampToDate(timestamp: number) {
|
||||
return new Date(timestamp * 1000);
|
||||
@@ -9,5 +9,5 @@ export function isSignatureHeaderFormatValid(signature: string | undefined): sig
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeof signature === 'string' && signature.length > 0;
|
||||
return isNonEmptyString(signature);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { TaskPersistenceConfig, TaskServiceDriverDefinition } from '../../tasks.types';
|
||||
import { createLibSqlDriver, setupSchema } from '@cadence-mq/driver-libsql';
|
||||
import { createClient } from '@libsql/client';
|
||||
|
||||
export function createLibSqlTaskServiceDriver({ taskPersistenceConfig }: { taskPersistenceConfig: TaskPersistenceConfig }): TaskServiceDriverDefinition {
|
||||
const { url, authToken, pollIntervalMs } = taskPersistenceConfig.drivers.libSql;
|
||||
|
||||
const client = createClient({ url, authToken });
|
||||
const driver = createLibSqlDriver({ client, pollIntervalMs });
|
||||
|
||||
return {
|
||||
driver,
|
||||
initialize: async () => {
|
||||
await setupSchema({ client });
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { TaskServiceDriverDefinition } from '../../tasks.types';
|
||||
import { createMemoryDriver } from '@cadence-mq/driver-memory';
|
||||
|
||||
export function createMemoryTaskServiceDriver(): TaskServiceDriverDefinition {
|
||||
const driver = createMemoryDriver();
|
||||
|
||||
return {
|
||||
driver,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export const TASKS_DRIVER_NAMES = {
|
||||
memory: 'memory',
|
||||
libsql: 'libsql',
|
||||
} as const;
|
||||
|
||||
export const tasksDriverNames = Object.keys(TASKS_DRIVER_NAMES);
|
||||
|
||||
export type TasksDriverName = keyof typeof TASKS_DRIVER_NAMES;
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { TaskServiceDriverFactory } from '../tasks.types';
|
||||
import { createLibSqlTaskServiceDriver } from './libsql/libsql.tasks-driver';
|
||||
import { createMemoryTaskServiceDriver } from './memory/memory.tasks-driver';
|
||||
import { TASKS_DRIVER_NAMES } from './tasks-driver.constants';
|
||||
|
||||
export const tasksDrivers = {
|
||||
[TASKS_DRIVER_NAMES.memory]: createMemoryTaskServiceDriver,
|
||||
[TASKS_DRIVER_NAMES.libsql]: createLibSqlTaskServiceDriver,
|
||||
} as const satisfies Record<string, TaskServiceDriverFactory>;
|
||||
@@ -1,15 +1,39 @@
|
||||
import type { ConfigDefinition } from 'figue';
|
||||
import type { TasksDriverName } from './drivers/tasks-driver.constants';
|
||||
import { z } from 'zod';
|
||||
import { booleanishSchema } from '../config/config.schemas';
|
||||
import { tasksDriverNames } from './drivers/tasks-driver.constants';
|
||||
|
||||
export const tasksConfig = {
|
||||
persistence: {
|
||||
driver: {
|
||||
doc: 'The driver to use for the tasks persistence',
|
||||
schema: z.enum(['memory']),
|
||||
driverName: {
|
||||
doc: `The driver to use for the tasks persistence, values can be one of: ${tasksDriverNames.map(x => `\`${x}\``).join(', ')}. Using the memory driver is enough when running a single instance of the server.`,
|
||||
schema: z.enum(tasksDriverNames as [TasksDriverName, ...TasksDriverName[]]),
|
||||
default: 'memory',
|
||||
env: 'TASKS_PERSISTENCE_DRIVER',
|
||||
},
|
||||
drivers: {
|
||||
libSql: {
|
||||
url: {
|
||||
doc: 'The URL of the LibSQL database, can be either a file-protocol url with a local path or a remote LibSQL database URL',
|
||||
schema: z.string().url(),
|
||||
default: 'file:./tasks-db.sqlite',
|
||||
env: 'TASKS_PERSISTENCE_DRIVERS_LIBSQL_URL',
|
||||
},
|
||||
authToken: {
|
||||
doc: 'The auth token for the LibSQL database',
|
||||
schema: z.string().optional(),
|
||||
default: undefined,
|
||||
env: 'TASKS_PERSISTENCE_DRIVERS_LIBSQL_AUTH_TOKEN',
|
||||
},
|
||||
pollIntervalMs: {
|
||||
doc: 'The interval at which the task persistence driver polls for new tasks',
|
||||
schema: z.coerce.number().int().positive(),
|
||||
default: 1_000,
|
||||
env: 'TASKS_PERSISTENCE_DRIVERS_LIBSQL_POLL_INTERVAL_MS',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
worker: {
|
||||
id: {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { Config } from '../config/config.types';
|
||||
import { createCadence } from '@cadence-mq/core';
|
||||
import { createMemoryDriver } from '@cadence-mq/driver-memory';
|
||||
import { createError } from '../shared/errors/errors';
|
||||
import { createLogger } from '../shared/logger/logger';
|
||||
import { isNil } from '../shared/utils';
|
||||
import { tasksDrivers } from './drivers/tasks-driver.registry';
|
||||
|
||||
export type TaskServices = ReturnType<typeof createTaskServices>;
|
||||
|
||||
@@ -9,12 +11,30 @@ const logger = createLogger({ namespace: 'tasks:services' });
|
||||
|
||||
export function createTaskServices({ config }: { config: Config }) {
|
||||
const workerId = config.tasks.worker.id ?? 'default';
|
||||
const taskPersistenceConfig = config.tasks.persistence;
|
||||
const { driverName } = taskPersistenceConfig;
|
||||
|
||||
const driver = createMemoryDriver();
|
||||
const driverFactory = tasksDrivers[driverName];
|
||||
|
||||
if (isNil(driverFactory)) {
|
||||
// Should not happen as the config validation should catch invalid driver names
|
||||
throw createError({
|
||||
message: `Invalid task service driver: ${driverName}`,
|
||||
code: 'tasks.invalid_driver',
|
||||
statusCode: 500,
|
||||
isInternal: true,
|
||||
});
|
||||
}
|
||||
|
||||
const { driver, initialize } = driverFactory({ taskPersistenceConfig });
|
||||
const cadence = createCadence({ driver, logger });
|
||||
|
||||
return {
|
||||
...cadence,
|
||||
initialize: async () => {
|
||||
await initialize?.();
|
||||
logger.debug({ driverName }, 'Task persistence driver initialized');
|
||||
},
|
||||
start: () => {
|
||||
const worker = cadence.createWorker({ workerId });
|
||||
|
||||
|
||||
7
apps/papra-server/src/modules/tasks/tasks.types.ts
Normal file
7
apps/papra-server/src/modules/tasks/tasks.types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { JobRepositoryDriver } from '@cadence-mq/core';
|
||||
import type { Config } from '../config/config.types';
|
||||
|
||||
export type TaskPersistenceConfig = Config['tasks']['persistence'];
|
||||
|
||||
export type TaskServiceDriverDefinition = { driver: JobRepositoryDriver; initialize?: () => Promise<void> };
|
||||
export type TaskServiceDriverFactory = (args: { taskPersistenceConfig: TaskPersistenceConfig }) => TaskServiceDriverDefinition;
|
||||
@@ -1,3 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({});
|
||||
export default defineConfig({
|
||||
test: {
|
||||
env: {
|
||||
TZ: 'UTC',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"version": "changeset version && pnpm install --no-frozen-lockfile",
|
||||
"changeset": "changeset",
|
||||
"build:packages": "pnpm --filter './packages/*' --stream build",
|
||||
"test": "TZ=UTC vitest run",
|
||||
"test:watch": "TZ=UTC vitest watch"
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-github": "^0.5.1",
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({});
|
||||
export default defineConfig({
|
||||
test: {
|
||||
env: {
|
||||
TZ: 'UTC',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({});
|
||||
export default defineConfig({
|
||||
test: {
|
||||
env: {
|
||||
TZ: 'UTC',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({});
|
||||
export default defineConfig({
|
||||
test: {
|
||||
env: {
|
||||
TZ: 'UTC',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({});
|
||||
export default defineConfig({
|
||||
test: {
|
||||
env: {
|
||||
TZ: 'UTC',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
156
pnpm-lock.yaml
generated
156
pnpm-lock.yaml
generated
@@ -253,6 +253,9 @@ importers:
|
||||
'@cadence-mq/core':
|
||||
specifier: ^0.2.1
|
||||
version: 0.2.1
|
||||
'@cadence-mq/driver-libsql':
|
||||
specifier: ^0.2.1
|
||||
version: 0.2.1(@cadence-mq/core@0.2.1)(@libsql/client@0.14.0)
|
||||
'@cadence-mq/driver-memory':
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0(@cadence-mq/core@0.2.1)
|
||||
@@ -1019,6 +1022,11 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/parser@7.28.4':
|
||||
resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/plugin-syntax-jsx@7.25.9':
|
||||
resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -1045,6 +1053,10 @@ packages:
|
||||
resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.28.4':
|
||||
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@balena/dockerignore@1.0.2':
|
||||
resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==}
|
||||
|
||||
@@ -1061,6 +1073,12 @@ packages:
|
||||
'@cadence-mq/core@0.2.1':
|
||||
resolution: {integrity: sha512-Cu/jqR7mNhMZ1U4Boiudy2nePyf4PtqBUFGhUcsCQPJfymKcrDm4xjp8A/2tKZr5JSgkN/7L0/+mHZ27GVSryQ==}
|
||||
|
||||
'@cadence-mq/driver-libsql@0.2.1':
|
||||
resolution: {integrity: sha512-tQPmMNLLVEhvT2HdY/rHk+Cl0Yj4JFMQnoYnBYIw30kTIpKGCQWnBTf5oSmIlmc6wdIHYan+f+waVhWmkObD1w==}
|
||||
peerDependencies:
|
||||
'@cadence-mq/core': ^0.2.0
|
||||
'@libsql/client': ^0.15.9
|
||||
|
||||
'@cadence-mq/driver-memory@0.2.0':
|
||||
resolution: {integrity: sha512-U/L5nkCu+BYO814oQAYbFYSNASha+6Om3Px3+Jm47YzFmSrQhrnX6fTyICVQFz+MflWndhke6Bh1mwik6nbrcw==}
|
||||
peerDependencies:
|
||||
@@ -1176,6 +1194,10 @@ packages:
|
||||
resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@csstools/color-helpers@5.1.0':
|
||||
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@csstools/css-calc@2.1.3':
|
||||
resolution: {integrity: sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1190,13 +1212,6 @@ packages:
|
||||
'@csstools/css-parser-algorithms': ^3.0.5
|
||||
'@csstools/css-tokenizer': ^3.0.4
|
||||
|
||||
'@csstools/css-color-parser@3.0.10':
|
||||
resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@csstools/css-parser-algorithms': ^3.0.5
|
||||
'@csstools/css-tokenizer': ^3.0.4
|
||||
|
||||
'@csstools/css-color-parser@3.0.9':
|
||||
resolution: {integrity: sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1204,6 +1219,13 @@ packages:
|
||||
'@csstools/css-parser-algorithms': ^3.0.4
|
||||
'@csstools/css-tokenizer': ^3.0.3
|
||||
|
||||
'@csstools/css-color-parser@3.1.0':
|
||||
resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@csstools/css-parser-algorithms': ^3.0.5
|
||||
'@csstools/css-tokenizer': ^3.0.4
|
||||
|
||||
'@csstools/css-parser-algorithms@3.0.4':
|
||||
resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2440,6 +2462,9 @@ packages:
|
||||
'@jridgewell/sourcemap-codec@1.5.4':
|
||||
resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
@@ -3981,8 +4006,8 @@ packages:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
aproba@2.0.0:
|
||||
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
|
||||
aproba@2.1.0:
|
||||
resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==}
|
||||
|
||||
archiver-utils@5.0.2:
|
||||
resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==}
|
||||
@@ -4542,9 +4567,21 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decimal.js@10.5.0:
|
||||
resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
|
||||
|
||||
decimal.js@10.6.0:
|
||||
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
|
||||
|
||||
decode-named-character-reference@1.0.2:
|
||||
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
|
||||
|
||||
@@ -4606,6 +4643,10 @@ packages:
|
||||
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
detect-libc@2.0.4:
|
||||
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
deterministic-object-hash@2.0.2:
|
||||
resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5422,8 +5463,8 @@ packages:
|
||||
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
form-data@4.0.3:
|
||||
resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==}
|
||||
form-data@4.0.4:
|
||||
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
format@0.2.2:
|
||||
@@ -6108,6 +6149,9 @@ packages:
|
||||
magic-string@0.30.17:
|
||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||
|
||||
magic-string@0.30.19:
|
||||
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
|
||||
|
||||
magicast@0.3.5:
|
||||
resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
|
||||
|
||||
@@ -6432,6 +6476,9 @@ packages:
|
||||
nan@2.22.0:
|
||||
resolution: {integrity: sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==}
|
||||
|
||||
nan@2.23.0:
|
||||
resolution: {integrity: sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@@ -6527,6 +6574,9 @@ packages:
|
||||
nwsapi@2.2.20:
|
||||
resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==}
|
||||
|
||||
nwsapi@2.2.22:
|
||||
resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==}
|
||||
|
||||
nypm@0.6.0:
|
||||
resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==}
|
||||
engines: {node: ^14.16.0 || >=16.10.0}
|
||||
@@ -8573,7 +8623,7 @@ snapshots:
|
||||
'@asamuzakjp/css-color@3.2.0':
|
||||
dependencies:
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
lru-cache: 10.4.3
|
||||
@@ -9390,6 +9440,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.28.0
|
||||
|
||||
'@babel/parser@7.28.4':
|
||||
dependencies:
|
||||
'@babel/types': 7.28.4
|
||||
|
||||
'@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
@@ -9427,6 +9481,11 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
|
||||
'@babel/types@7.28.4':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
|
||||
'@balena/dockerignore@1.0.2': {}
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
@@ -9445,6 +9504,11 @@ snapshots:
|
||||
'@standard-schema/spec': 1.0.0
|
||||
cron-parser: 5.3.0
|
||||
|
||||
'@cadence-mq/driver-libsql@0.2.1(@cadence-mq/core@0.2.1)(@libsql/client@0.14.0)':
|
||||
dependencies:
|
||||
'@cadence-mq/core': 0.2.1
|
||||
'@libsql/client': 0.14.0
|
||||
|
||||
'@cadence-mq/driver-memory@0.2.0(@cadence-mq/core@0.2.1)':
|
||||
dependencies:
|
||||
'@cadence-mq/core': 0.2.1
|
||||
@@ -9666,6 +9730,9 @@ snapshots:
|
||||
|
||||
'@csstools/color-helpers@5.0.2': {}
|
||||
|
||||
'@csstools/color-helpers@5.1.0':
|
||||
optional: true
|
||||
|
||||
'@csstools/css-calc@2.1.3(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
|
||||
dependencies:
|
||||
'@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
|
||||
@@ -9677,14 +9744,6 @@ snapshots:
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
optional: true
|
||||
|
||||
'@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
|
||||
dependencies:
|
||||
'@csstools/color-helpers': 5.0.2
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
optional: true
|
||||
|
||||
'@csstools/css-color-parser@3.0.9(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
|
||||
dependencies:
|
||||
'@csstools/color-helpers': 5.0.2
|
||||
@@ -9692,6 +9751,14 @@ snapshots:
|
||||
'@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
|
||||
'@csstools/css-tokenizer': 3.0.3
|
||||
|
||||
'@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
|
||||
dependencies:
|
||||
'@csstools/color-helpers': 5.1.0
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
optional: true
|
||||
|
||||
'@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)':
|
||||
dependencies:
|
||||
'@csstools/css-tokenizer': 3.0.3
|
||||
@@ -10536,6 +10603,8 @@ snapshots:
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.4': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
@@ -10672,7 +10741,7 @@ snapshots:
|
||||
|
||||
'@mapbox/node-pre-gyp@1.0.11':
|
||||
dependencies:
|
||||
detect-libc: 2.0.3
|
||||
detect-libc: 2.0.4
|
||||
https-proxy-agent: 5.0.1
|
||||
make-dir: 3.1.0
|
||||
node-fetch: 2.7.0
|
||||
@@ -12348,7 +12417,7 @@ snapshots:
|
||||
|
||||
'@vue/compiler-core@3.5.13':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.0
|
||||
'@babel/parser': 7.28.4
|
||||
'@vue/shared': 3.5.13
|
||||
entities: 4.5.0
|
||||
estree-walker: 2.0.2
|
||||
@@ -12361,13 +12430,13 @@ snapshots:
|
||||
|
||||
'@vue/compiler-sfc@3.5.13':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.0
|
||||
'@babel/parser': 7.28.4
|
||||
'@vue/compiler-core': 3.5.13
|
||||
'@vue/compiler-dom': 3.5.13
|
||||
'@vue/compiler-ssr': 3.5.13
|
||||
'@vue/shared': 3.5.13
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.17
|
||||
magic-string: 0.30.19
|
||||
postcss: 8.5.6
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@@ -12421,7 +12490,7 @@ snapshots:
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
@@ -12469,7 +12538,7 @@ snapshots:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
aproba@2.0.0:
|
||||
aproba@2.1.0:
|
||||
optional: true
|
||||
|
||||
archiver-utils@5.0.2:
|
||||
@@ -12914,7 +12983,7 @@ snapshots:
|
||||
canvas@2.11.2:
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 1.0.11
|
||||
nan: 2.22.0
|
||||
nan: 2.23.0
|
||||
simple-get: 3.1.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
@@ -13167,8 +13236,16 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
optional: true
|
||||
|
||||
decimal.js@10.5.0: {}
|
||||
|
||||
decimal.js@10.6.0:
|
||||
optional: true
|
||||
|
||||
decode-named-character-reference@1.0.2:
|
||||
dependencies:
|
||||
character-entities: 2.0.2
|
||||
@@ -13213,6 +13290,9 @@ snapshots:
|
||||
|
||||
detect-libc@2.0.3: {}
|
||||
|
||||
detect-libc@2.0.4:
|
||||
optional: true
|
||||
|
||||
deterministic-object-hash@2.0.2:
|
||||
dependencies:
|
||||
base-64: 1.0.0
|
||||
@@ -14324,7 +14404,7 @@ snapshots:
|
||||
es-set-tostringtag: 2.1.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
form-data@4.0.3:
|
||||
form-data@4.0.4:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
@@ -14371,7 +14451,7 @@ snapshots:
|
||||
|
||||
gauge@3.0.2:
|
||||
dependencies:
|
||||
aproba: 2.0.0
|
||||
aproba: 2.1.0
|
||||
color-support: 1.1.3
|
||||
console-control-strings: 1.1.0
|
||||
has-unicode: 2.0.1
|
||||
@@ -14763,7 +14843,7 @@ snapshots:
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.4.1
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
@@ -15003,13 +15083,13 @@ snapshots:
|
||||
dependencies:
|
||||
cssstyle: 4.6.0
|
||||
data-urls: 5.0.0
|
||||
decimal.js: 10.5.0
|
||||
form-data: 4.0.3
|
||||
decimal.js: 10.6.0
|
||||
form-data: 4.0.4
|
||||
html-encoding-sniffer: 4.0.0
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
is-potential-custom-element-name: 1.0.1
|
||||
nwsapi: 2.2.20
|
||||
nwsapi: 2.2.22
|
||||
parse5: 7.3.0
|
||||
rrweb-cssom: 0.8.0
|
||||
saxes: 6.0.0
|
||||
@@ -15152,6 +15232,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
magic-string@0.30.19:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
magicast@0.3.5:
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.0
|
||||
@@ -15800,6 +15884,9 @@ snapshots:
|
||||
nan@2.22.0:
|
||||
optional: true
|
||||
|
||||
nan@2.23.0:
|
||||
optional: true
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
nanoid@5.1.5: {}
|
||||
@@ -15876,6 +15963,9 @@ snapshots:
|
||||
|
||||
nwsapi@2.2.20: {}
|
||||
|
||||
nwsapi@2.2.22:
|
||||
optional: true
|
||||
|
||||
nypm@0.6.0:
|
||||
dependencies:
|
||||
citty: 0.1.6
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
|
||||
test: {
|
||||
reporters: ['verbose'],
|
||||
projects: ['apps/*', 'packages/*'],
|
||||
coverage: {
|
||||
include: ['packages/*/src'],
|
||||
}
|
||||
},
|
||||
env: {
|
||||
TZ: 'UTC',
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user