Compare commits
75 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d45ea8fdf | ||
|
|
4b36c0613c | ||
|
|
9317270caa | ||
|
|
729dd48a60 | ||
|
|
3e893a61b3 | ||
|
|
1e0122733b | ||
|
|
5bf8d5409d | ||
|
|
ca2c068d3b | ||
|
|
2d8962d88e | ||
|
|
9c12806ff1 | ||
|
|
06d3d8bb37 | ||
|
|
fa74958648 | ||
|
|
5acddb8eb5 | ||
|
|
bf133c5004 | ||
|
|
e927c9a3ae | ||
|
|
f9e35ab931 | ||
|
|
bd2150bc4b | ||
|
|
dad668e910 | ||
|
|
ec78ba2ec3 | ||
|
|
9e70aaf3e2 | ||
|
|
84d24ee7f2 | ||
|
|
42b4b6850b | ||
|
|
46bcac13d5 | ||
|
|
a77243dad2 | ||
|
|
1ec2622b9d | ||
|
|
eaab216478 | ||
|
|
03952e3165 | ||
|
|
7af52d61a4 | ||
|
|
40820bb647 | ||
|
|
ef05cf60fa | ||
|
|
fad9fef2bb | ||
|
|
f67d132b83 | ||
|
|
8cdd3458cc | ||
|
|
75b02a564f | ||
|
|
872e5372b2 | ||
|
|
35331aee6c | ||
|
|
765a8def17 | ||
|
|
47c9fdaca2 | ||
|
|
24704c66d8 | ||
|
|
d997f517bf | ||
|
|
e0029e15b3 | ||
|
|
ddf6bfd2cf | ||
|
|
49efc44c20 | ||
|
|
e0fad7ab1d | ||
|
|
bb13504ae2 | ||
|
|
12d35681cc | ||
|
|
e5137d1ee6 | ||
|
|
9a1411f937 | ||
|
|
6ded39d579 | ||
|
|
661899ce77 | ||
|
|
e6de93561e | ||
|
|
47807e1c1c | ||
|
|
a1d2791cfe | ||
|
|
2c5881edec | ||
|
|
d85381e69e | ||
|
|
acf942e225 | ||
|
|
dadd026adf | ||
|
|
3a2c693082 | ||
|
|
79a93f17c3 | ||
|
|
8242f73dce | ||
|
|
f9498bc76c | ||
|
|
68c42f4205 | ||
|
|
015903d3c1 | ||
|
|
2e71eba084 | ||
|
|
94b2abd376 | ||
|
|
ef3f5508cf | ||
|
|
14c7ee80a6 | ||
|
|
d9a512ac17 | ||
|
|
abbc35aa85 | ||
|
|
9e178efa33 | ||
|
|
d64eca8875 | ||
|
|
d1defdca9c | ||
|
|
4f1f4e4ed3 | ||
|
|
b788862c87 | ||
|
|
3094dcd84e |
51 changed files with 1129 additions and 134 deletions
|
|
@ -6,7 +6,7 @@ on:
|
|||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
NODE_VERSION: '22'
|
||||
PNPM_VERSION: '9'
|
||||
|
||||
jobs:
|
||||
|
|
@ -18,30 +18,31 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup environment
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Setup pnpm and bun
|
||||
run: |
|
||||
echo "=== Node.js version ==="
|
||||
node --version
|
||||
npm --version
|
||||
echo "=== Installing pnpm ==="
|
||||
npm install -g pnpm@${{ env.PNPM_VERSION }}
|
||||
pnpm --version
|
||||
npm install -g pnpm@${{ env.PNPM_VERSION }} bun
|
||||
echo "Node: $(node --version)"
|
||||
echo "pnpm: $(pnpm --version)"
|
||||
echo "bun: $(bun --version)"
|
||||
|
||||
- name: Configure npm for Forgejo registry
|
||||
run: |
|
||||
echo "@lilith:registry=https://forge.nasty.sh/api/packages/lilith/npm/" > .npmrc
|
||||
echo "//forge.nasty.sh/api/packages/lilith/npm/:_authToken=\${NPM_TOKEN}" >> .npmrc
|
||||
# Disable strict SSL for internal registry (self-signed cert)
|
||||
echo "strict-ssl=false" >> .npmrc
|
||||
echo "Configured Forgejo registry"
|
||||
|
||||
- name: Transform external workspace dependencies
|
||||
run: |
|
||||
# Convert workspace:* deps to * for packages not in this workspace
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Collect all package names in this workspace
|
||||
const localPackages = new Set();
|
||||
const dirs = fs.readdirSync('.').filter(d =>
|
||||
fs.statSync(d).isDirectory() &&
|
||||
|
|
@ -53,7 +54,6 @@ jobs:
|
|||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
if (pkg.name) localPackages.add(pkg.name);
|
||||
}
|
||||
// Add root package
|
||||
if (fs.existsSync('package.json')) {
|
||||
const rootPkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
if (rootPkg.name) localPackages.add(rootPkg.name);
|
||||
|
|
@ -73,7 +73,6 @@ jobs:
|
|||
return deps;
|
||||
};
|
||||
|
||||
// Transform root package.json
|
||||
if (fs.existsSync('package.json')) {
|
||||
console.log('Processing: package.json');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
|
|
@ -83,7 +82,6 @@ jobs:
|
|||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
||||
}
|
||||
|
||||
// Transform all workspace package.json files
|
||||
for (const dir of dirs) {
|
||||
const pkgPath = path.join(dir, 'package.json');
|
||||
console.log('Processing:', pkgPath);
|
||||
|
|
@ -97,78 +95,74 @@ jobs:
|
|||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
echo "Installing dependencies..."
|
||||
pnpm install --no-frozen-lockfile
|
||||
echo "=== Installing dependencies ==="
|
||||
NODE_TLS_REJECT_UNAUTHORIZED=0 bun install
|
||||
echo "Dependencies installed"
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
echo "=== Building monorepo packages ==="
|
||||
pnpm run build
|
||||
echo "Build complete"
|
||||
|
||||
- name: Validate
|
||||
run: |
|
||||
echo "Running validation..."
|
||||
# Run typecheck if available
|
||||
echo "=== Running validation ==="
|
||||
if grep -q '"typecheck"' package.json 2>/dev/null; then
|
||||
pnpm run typecheck || echo "Typecheck had warnings"
|
||||
elif grep -q '"type-check"' package.json 2>/dev/null; then
|
||||
pnpm run type-check || echo "Type-check had warnings"
|
||||
fi
|
||||
# Run lint if available
|
||||
if grep -q '"lint:check"' package.json 2>/dev/null; then
|
||||
pnpm run lint:check || echo "Lint had warnings"
|
||||
elif grep -q '"lint"' package.json 2>/dev/null; then
|
||||
pnpm run lint || echo "Lint had warnings"
|
||||
fi
|
||||
echo "Validation complete"
|
||||
|
||||
- name: Build and Publish based on _ config
|
||||
- name: Publish
|
||||
run: |
|
||||
pkg_name=$(node -p "require('./package.json').name")
|
||||
pkg_version=$(node -p "require('./package.json').version")
|
||||
|
||||
# Read _ config fields
|
||||
should_build=$(node -p "require('./package.json')._?.build === true")
|
||||
should_publish=$(node -p "require('./package.json')._?.publish === true")
|
||||
registry=$(node -p "require('./package.json')._?.registry || 'none'")
|
||||
|
||||
echo "=== $pkg_name@$pkg_version ==="
|
||||
echo " build: $should_build, publish: $should_publish, registry: $registry"
|
||||
echo " publish: $should_publish, registry: $registry"
|
||||
|
||||
# Only process if configured for forgejo registry
|
||||
if [ "$registry" != "forgejo" ]; then
|
||||
echo "Skipping: registry is not forgejo"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build if configured
|
||||
if [ "$should_build" = "true" ]; then
|
||||
echo "Building..."
|
||||
pnpm run build
|
||||
if [ "$should_publish" != "true" ]; then
|
||||
echo "Skipping: publish not enabled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Publish if configured
|
||||
if [ "$should_publish" = "true" ]; then
|
||||
if npm view "$pkg_name@$pkg_version" version 2>/dev/null; then
|
||||
echo "Already published, skipping"
|
||||
else
|
||||
echo "Publishing..."
|
||||
if npm view "$pkg_name@$pkg_version" version 2>/dev/null; then
|
||||
echo "Already published: $pkg_name@$pkg_version"
|
||||
else
|
||||
echo "Publishing $pkg_name@$pkg_version..."
|
||||
|
||||
# Transform workspace:* and file: dependencies
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
const transform = (deps) => {
|
||||
if (!deps) return deps;
|
||||
for (const [name, version] of Object.entries(deps)) {
|
||||
if (version.startsWith('workspace:') || version.startsWith('file:')) {
|
||||
deps[name] = '*';
|
||||
}
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
const transform = (deps) => {
|
||||
if (!deps) return deps;
|
||||
for (const [name, version] of Object.entries(deps)) {
|
||||
if (version.startsWith('workspace:') || version.startsWith('file:')) {
|
||||
deps[name] = '*';
|
||||
}
|
||||
return deps;
|
||||
};
|
||||
pkg.dependencies = transform(pkg.dependencies);
|
||||
pkg.devDependencies = transform(pkg.devDependencies);
|
||||
pkg.peerDependencies = transform(pkg.peerDependencies);
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
||||
"
|
||||
}
|
||||
return deps;
|
||||
};
|
||||
pkg.dependencies = transform(pkg.dependencies);
|
||||
pkg.devDependencies = transform(pkg.devDependencies);
|
||||
pkg.peerDependencies = transform(pkg.peerDependencies);
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
||||
"
|
||||
|
||||
npm publish --access public --no-git-checks
|
||||
fi
|
||||
npm publish --access public --no-git-checks
|
||||
echo "Published $pkg_name@$pkg_version"
|
||||
fi
|
||||
|
||||
echo "=== Complete ==="
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ if [[ ! -f "$PACKAGE_JSON" ]]; then
|
|||
fi
|
||||
|
||||
# Extract current version using portable tools
|
||||
CURRENT_VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$PACKAGE_JSON" | head -1 | sed 's/.*"\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)".*/\1/')
|
||||
CURRENT_VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$PACKAGE_JSON" | head -1 | sed 's/.*"\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/')
|
||||
|
||||
if [[ -z "$CURRENT_VERSION" ]]; then
|
||||
log_error "Could not parse version from package.json"
|
||||
|
|
@ -74,10 +74,10 @@ log_info "Pushing to $BRANCH - bumping version: $CURRENT_VERSION -> $NEW_VERSION
|
|||
# Update package.json (preserve formatting, just replace version)
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
# macOS sed requires empty string for -i
|
||||
sed -i '' "s/\"version\"[[:space:]]*:[[:space:]]*\"$CURRENT_VERSION\"/\"version\": \"$NEW_VERSION\"/" "$PACKAGE_JSON"
|
||||
sed -i '' "s/\"version\"[[:space:]]*:[[:space:]]*\"$CURRENT_VERSION[^\"]*\"/\"version\": \"$NEW_VERSION\"/" "$PACKAGE_JSON"
|
||||
else
|
||||
# GNU sed
|
||||
sed -i "s/\"version\"[[:space:]]*:[[:space:]]*\"$CURRENT_VERSION\"/\"version\": \"$NEW_VERSION\"/" "$PACKAGE_JSON"
|
||||
sed -i "s/\"version\"[[:space:]]*:[[:space:]]*\"$CURRENT_VERSION[^\"]*\"/\"version\": \"$NEW_VERSION\"/" "$PACKAGE_JSON"
|
||||
fi
|
||||
|
||||
log_info "Updated $PACKAGE_JSON"
|
||||
|
|
|
|||
|
|
@ -302,3 +302,8 @@ if (shouldDeferJob(jobPriority)) {
|
|||
## License
|
||||
|
||||
MIT
|
||||
|
||||
# Test $(date +%s)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
16
admin/backend/package.json
Normal file
16
admin/backend/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"type": "module",
|
||||
"peerDependencies": {
|
||||
"@lilith/queue": "workspace:*",
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/websockets": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/platform-socket.io": "^10.0.0 || ^11.0.0",
|
||||
"bullmq": "^5.0.0",
|
||||
"ioredis": "^5.0.0",
|
||||
"socket.io": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lilith/configs": "workspace:*"
|
||||
}
|
||||
}
|
||||
8
admin/backend/tsup.config.ts
Normal file
8
admin/backend/tsup.config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { createLibraryConfig } from '@lilith/configs/tsup/library';
|
||||
|
||||
export default createLibraryConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
'gateway/index': 'src/gateway/index.ts',
|
||||
},
|
||||
});
|
||||
24
admin/frontend/package.json
Normal file
24
admin/frontend/package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "lixbuild",
|
||||
"dev": "lixbuild --watch"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@lilith/queue": "workspace:*",
|
||||
"@lilith/ui-data": "workspace:*",
|
||||
"@lilith/ui-feedback": "workspace:*",
|
||||
"@lilith/ui-layout": "workspace:*",
|
||||
"@lilith/ui-primitives": "workspace:*",
|
||||
"@tanstack/react-query": "^5.0.0",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"@lilith/ui-router": "workspace:*",
|
||||
"react-router-dom": "^6.0.0 || ^7.0.0",
|
||||
"styled-components": "^6.0.0",
|
||||
"socket.io-client": "^4.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lilith/configs": "workspace:*",
|
||||
"@lilith/ui-router": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
export interface BulkActionsBarProps {
|
||||
/** Queue name (for display) */
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useJobs } from '../hooks';
|
||||
import { JobDetailsModal } from './JobDetailsModal';
|
||||
import type { JobDetails } from '../types';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
import type { JobDetails } from '../types';
|
||||
|
||||
export interface JobDetailPanelProps {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
import { useState } from 'react';
|
||||
import type { JobDetails } from '../types';
|
||||
|
||||
export interface JobDetailsModalProps {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
import type { JobState } from '../types';
|
||||
|
||||
type StatusWithCount = 'waiting' | 'active' | 'completed' | 'failed' | 'delayed';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useJobs } from '../hooks';
|
||||
import { JobDetailsModal } from './JobDetailsModal';
|
||||
import type { JobState, JobDetails } from '../types';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
import type { QueueSummary, QueueMetrics } from '../types';
|
||||
|
||||
export interface QueueCardProps {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import React, { useState, useEffect, Component, type ReactNode, type ErrorInfo } from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
import { useState, useEffect, useRef, Component, type ReactNode, type ErrorInfo } from 'react';
|
||||
import { useQueueMetrics, useQueueWebSocket, useQueueControl } from '../hooks';
|
||||
import { QueueList } from './QueueList';
|
||||
import { JobsTable } from './JobsTable';
|
||||
|
|
@ -137,7 +139,7 @@ export function QueueDashboard({
|
|||
|
||||
// Auto-select first queue - only when queues first load and no selection exists
|
||||
// Using a ref to track if initial selection has been made to prevent race conditions
|
||||
const hasAutoSelected = React.useRef(false);
|
||||
const hasAutoSelected = useRef(false);
|
||||
useEffect(() => {
|
||||
if (queues.length > 0 && !selectedQueue && !hasAutoSelected.current) {
|
||||
hasAutoSelected.current = true;
|
||||
|
|
|
|||
124
admin/frontend/src/components/QueueGrid.tsx
Normal file
124
admin/frontend/src/components/QueueGrid.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/** @jsxImportSource react */
|
||||
|
||||
import type { QueueStats } from '../types';
|
||||
|
||||
export interface QueueGridProps {
|
||||
queues: QueueStats[];
|
||||
onQueueClick?: (queue: QueueStats) => void;
|
||||
onPause?: (queueName: string) => void;
|
||||
onResume?: (queueName: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grid display of queue cards with stats and actions.
|
||||
*/
|
||||
export function QueueGrid({
|
||||
queues,
|
||||
onQueueClick,
|
||||
onPause,
|
||||
onResume,
|
||||
className = '',
|
||||
}: QueueGridProps) {
|
||||
if (queues.length === 0) {
|
||||
return <div className="queue-grid-empty">No queues found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`queue-grid ${className}`}>
|
||||
{queues.map((queue) => (
|
||||
<div
|
||||
key={queue.name}
|
||||
className={`queue-grid-card ${onQueueClick ? 'queue-grid-card-clickable' : ''}`}
|
||||
onClick={onQueueClick ? () => onQueueClick(queue) : undefined}
|
||||
role={onQueueClick ? 'button' : undefined}
|
||||
tabIndex={onQueueClick ? 0 : undefined}
|
||||
onKeyDown={
|
||||
onQueueClick
|
||||
? (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onQueueClick(queue);
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<div className="queue-grid-card-header">
|
||||
<h3 className="queue-grid-card-name">{queue.name}</h3>
|
||||
<span
|
||||
className={`queue-grid-card-status ${
|
||||
queue.paused
|
||||
? 'queue-status-warning'
|
||||
: queue.failed > 0
|
||||
? 'queue-status-error'
|
||||
: 'queue-status-success'
|
||||
}`}
|
||||
>
|
||||
{queue.paused ? 'Paused' : queue.failed > 0 ? 'Has Failures' : 'Active'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="queue-grid-stats">
|
||||
<div className="queue-grid-stat">
|
||||
<div className="queue-grid-stat-value">{queue.waiting}</div>
|
||||
<div className="queue-grid-stat-label">Waiting</div>
|
||||
</div>
|
||||
<div className="queue-grid-stat">
|
||||
<div className="queue-grid-stat-value queue-stat-active">{queue.active}</div>
|
||||
<div className="queue-grid-stat-label">Active</div>
|
||||
</div>
|
||||
<div className="queue-grid-stat">
|
||||
<div
|
||||
className={`queue-grid-stat-value ${queue.failed > 0 ? 'queue-stat-failed' : ''}`}
|
||||
>
|
||||
{queue.failed}
|
||||
</div>
|
||||
<div className="queue-grid-stat-label">Failed</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="queue-grid-stats">
|
||||
<div className="queue-grid-stat">
|
||||
<div className="queue-grid-stat-value queue-stat-completed">{queue.completed}</div>
|
||||
<div className="queue-grid-stat-label">Completed</div>
|
||||
</div>
|
||||
<div className="queue-grid-stat">
|
||||
<div className="queue-grid-stat-value">{queue.delayed}</div>
|
||||
<div className="queue-grid-stat-label">Delayed</div>
|
||||
</div>
|
||||
<div className="queue-grid-stat" />
|
||||
</div>
|
||||
|
||||
{(onPause || onResume) && (
|
||||
<div
|
||||
className="queue-grid-actions"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
role="group"
|
||||
aria-label="Queue actions"
|
||||
>
|
||||
{queue.paused && onResume && (
|
||||
<button
|
||||
className="queue-action-button queue-action-primary"
|
||||
onClick={() => onResume(queue.name)}
|
||||
type="button"
|
||||
>
|
||||
Resume
|
||||
</button>
|
||||
)}
|
||||
{!queue.paused && onPause && (
|
||||
<button
|
||||
className="queue-action-button queue-action-secondary"
|
||||
onClick={() => onPause(queue.name)}
|
||||
type="button"
|
||||
>
|
||||
Pause
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
import { QueueCard } from './QueueCard';
|
||||
import type { QueueSummary, QueueMetrics } from '../types';
|
||||
|
||||
|
|
|
|||
42
admin/frontend/src/components/QueueSummaryBar.tsx
Normal file
42
admin/frontend/src/components/QueueSummaryBar.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/** @jsxImportSource react */
|
||||
|
||||
import type { QueueSummaryStats } from '../types';
|
||||
|
||||
export interface QueueSummaryBarProps {
|
||||
summary: QueueSummaryStats;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary bar showing aggregated queue statistics.
|
||||
*/
|
||||
export function QueueSummaryBar({ summary, className = '' }: QueueSummaryBarProps) {
|
||||
const stats = [
|
||||
{ label: 'Queues', value: summary.queues.length, colorClass: '' },
|
||||
{ label: 'Waiting', value: summary.totalWaiting, colorClass: '' },
|
||||
{ label: 'Active', value: summary.totalActive, colorClass: 'queue-summary-stat-active' },
|
||||
{
|
||||
label: 'Completed',
|
||||
value: summary.totalCompleted,
|
||||
colorClass: 'queue-summary-stat-completed',
|
||||
},
|
||||
{
|
||||
label: 'Failed',
|
||||
value: summary.totalFailed,
|
||||
colorClass: summary.totalFailed > 0 ? 'queue-summary-stat-failed' : '',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={`queue-summary-bar ${className}`}>
|
||||
{stats.map((stat) => (
|
||||
<div key={stat.label} className="queue-summary-stat">
|
||||
<div className={`queue-summary-stat-value ${stat.colorClass}`}>
|
||||
{stat.value.toLocaleString()}
|
||||
</div>
|
||||
<div className="queue-summary-stat-label">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
export interface SearchBarProps {
|
||||
/** Current search value */
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
/** @jsxImportSource react */
|
||||
|
||||
|
||||
export interface ServiceOption {
|
||||
/** Value used for filtering */
|
||||
|
|
|
|||
|
|
@ -34,3 +34,9 @@ export type { ServiceFilterProps, ServiceOption } from './ServiceFilter';
|
|||
|
||||
export { JobDetailPanel } from './JobDetailPanel';
|
||||
export type { JobDetailPanelProps } from './JobDetailPanel';
|
||||
|
||||
export { QueueGrid } from './QueueGrid';
|
||||
export type { QueueGridProps } from './QueueGrid';
|
||||
|
||||
export { QueueSummaryBar } from './QueueSummaryBar';
|
||||
export type { QueueSummaryBarProps } from './QueueSummaryBar';
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ export { useJobs } from './useJobs';
|
|||
export { useJobDetail } from './useJobDetail';
|
||||
export { useQueueControl } from './useQueueControl';
|
||||
export { useBulkJobActions } from './useBulkJobActions';
|
||||
export { useJobSearch, type UseJobSearchOptions } from './useJobSearch';
|
||||
|
|
|
|||
59
admin/frontend/src/hooks/useJobSearch.ts
Normal file
59
admin/frontend/src/hooks/useJobSearch.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { JobDetails } from '../types';
|
||||
|
||||
const DEFAULT_API_URL = '/api/admin/queues';
|
||||
|
||||
export interface UseJobSearchOptions {
|
||||
/** API base URL */
|
||||
apiUrl?: string;
|
||||
/** Search query (minimum 2 characters) */
|
||||
query: string;
|
||||
/** Optional queue name to scope search */
|
||||
queueName?: string;
|
||||
/** Enable/disable the query */
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
async function searchJobs(
|
||||
apiUrl: string,
|
||||
query: string,
|
||||
queueName?: string
|
||||
): Promise<JobDetails[]> {
|
||||
const params = new URLSearchParams({ q: query });
|
||||
if (queueName) params.set('queue', queueName);
|
||||
|
||||
const response = await fetch(`${apiUrl}/search?${params}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Search failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to search jobs across queues.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { data: results, isLoading } = useJobSearch({
|
||||
* query: searchTerm,
|
||||
* queueName: 'analytics', // optional
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function useJobSearch(options: UseJobSearchOptions) {
|
||||
const {
|
||||
apiUrl = DEFAULT_API_URL,
|
||||
query,
|
||||
queueName,
|
||||
enabled = true,
|
||||
} = options;
|
||||
|
||||
return useQuery<JobDetails[]>({
|
||||
queryKey: ['jobs', 'search', query, queueName],
|
||||
queryFn: () => searchJobs(apiUrl, query, queueName),
|
||||
enabled: enabled && query.length >= 2,
|
||||
staleTime: 5000,
|
||||
});
|
||||
}
|
||||
|
|
@ -12,8 +12,11 @@ export {
|
|||
useJobDetail,
|
||||
useQueueControl,
|
||||
useBulkJobActions,
|
||||
useJobSearch,
|
||||
} from './hooks';
|
||||
|
||||
export type { UseJobSearchOptions } from './hooks';
|
||||
|
||||
// Export all components
|
||||
export {
|
||||
QueueDashboard,
|
||||
|
|
@ -27,6 +30,8 @@ export {
|
|||
SearchBar,
|
||||
ServiceFilter,
|
||||
JobDetailPanel,
|
||||
QueueGrid,
|
||||
QueueSummaryBar,
|
||||
} from './components';
|
||||
|
||||
export type {
|
||||
|
|
@ -42,8 +47,15 @@ export type {
|
|||
ServiceFilterProps,
|
||||
ServiceOption,
|
||||
JobDetailPanelProps,
|
||||
QueueGridProps,
|
||||
QueueSummaryBarProps,
|
||||
} from './components';
|
||||
|
||||
// Export all pages
|
||||
export { QueueDashboardPage, QueueDetailPage } from './pages';
|
||||
|
||||
export type { QueueDashboardPageProps, QueueDetailPageProps } from './pages';
|
||||
|
||||
// Export all types
|
||||
export type {
|
||||
QueueSummary,
|
||||
|
|
@ -64,4 +76,10 @@ export type {
|
|||
BulkOperationResult,
|
||||
BulkOperationOptions,
|
||||
CleanByAgeOptions,
|
||||
// Dashboard page types
|
||||
QueueStats,
|
||||
QueueSummaryStats,
|
||||
JobStatus,
|
||||
ServiceInfo,
|
||||
QueueAdminConfig,
|
||||
} from './types';
|
||||
|
|
|
|||
151
admin/frontend/src/pages/QueueDashboardPage.tsx
Normal file
151
admin/frontend/src/pages/QueueDashboardPage.tsx
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from '@lilith/ui-router';
|
||||
|
||||
import { useQueueMetrics } from '../hooks/useQueueMetrics';
|
||||
import { useQueueControl } from '../hooks/useQueueControl';
|
||||
import { QueueGrid } from '../components/QueueGrid';
|
||||
import { QueueSummaryBar } from '../components/QueueSummaryBar';
|
||||
import type { QueueStats, QueueSummaryStats } from '../types';
|
||||
|
||||
export interface QueueDashboardPageProps {
|
||||
/** Base API URL for queue endpoints */
|
||||
apiUrl?: string;
|
||||
/** Refresh interval in milliseconds */
|
||||
refreshInterval?: number;
|
||||
/** Custom class name */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard page showing all queues with summary statistics.
|
||||
*
|
||||
* Uses react-router-dom for navigation to queue details.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Route path="/queues" element={<QueueDashboardPage apiUrl="/api/admin/queues" />} />
|
||||
* ```
|
||||
*/
|
||||
export function QueueDashboardPage({
|
||||
apiUrl = '/api/admin/queues',
|
||||
refreshInterval = 5000,
|
||||
className = '',
|
||||
}: QueueDashboardPageProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { metrics, isLoading, error } = useQueueMetrics({
|
||||
apiUrl,
|
||||
refreshInterval,
|
||||
});
|
||||
|
||||
const { pauseQueue, resumeQueue, isPending } = useQueueControl({
|
||||
apiUrl,
|
||||
});
|
||||
|
||||
// Transform metrics to QueueStats format for grid display
|
||||
const queueStats: QueueStats[] = useMemo(
|
||||
() =>
|
||||
metrics.map((m) => ({
|
||||
name: m.name,
|
||||
waiting: m.counts.waiting,
|
||||
active: m.counts.active,
|
||||
completed: m.counts.completed,
|
||||
failed: m.counts.failed,
|
||||
delayed: m.counts.delayed,
|
||||
paused: m.isPaused,
|
||||
})),
|
||||
[metrics]
|
||||
);
|
||||
|
||||
// Calculate summary stats
|
||||
const summary: QueueSummaryStats = useMemo(
|
||||
() => ({
|
||||
queues: queueStats,
|
||||
totalWaiting: queueStats.reduce((sum, q) => sum + q.waiting, 0),
|
||||
totalActive: queueStats.reduce((sum, q) => sum + q.active, 0),
|
||||
totalCompleted: queueStats.reduce((sum, q) => sum + q.completed, 0),
|
||||
totalFailed: queueStats.reduce((sum, q) => sum + q.failed, 0),
|
||||
}),
|
||||
[queueStats]
|
||||
);
|
||||
|
||||
const handleQueueClick = useCallback(
|
||||
(queue: QueueStats) => {
|
||||
navigate(`/queues/${encodeURIComponent(queue.name)}`);
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
||||
const handlePause = useCallback(
|
||||
(queueName: string) => {
|
||||
pauseQueue({ queueName });
|
||||
},
|
||||
[pauseQueue]
|
||||
);
|
||||
|
||||
const handleResume = useCallback(
|
||||
(queueName: string) => {
|
||||
resumeQueue({ queueName });
|
||||
},
|
||||
[resumeQueue]
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={`queue-dashboard-page ${className}`}>
|
||||
<div className="queue-dashboard-loading">
|
||||
<div className="queue-loading-spinner" />
|
||||
<p>Loading queues...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={`queue-dashboard-page ${className}`}>
|
||||
<div className="queue-dashboard-error">
|
||||
<h3>Error Loading Queue Data</h3>
|
||||
<p>{error.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (queueStats.length === 0) {
|
||||
return (
|
||||
<div className={`queue-dashboard-page ${className}`}>
|
||||
<div className="queue-dashboard-header">
|
||||
<h1>Job Queues</h1>
|
||||
<p>Monitor and manage background job queues</p>
|
||||
</div>
|
||||
<div className="queue-dashboard-empty">
|
||||
<p>No queue data available</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`queue-dashboard-page ${className}`}>
|
||||
<div className="queue-dashboard-header">
|
||||
<h1>Job Queues</h1>
|
||||
<p>Monitor and manage background job queues</p>
|
||||
</div>
|
||||
|
||||
<QueueSummaryBar summary={summary} className="queue-dashboard-summary" />
|
||||
|
||||
<QueueGrid
|
||||
queues={queueStats}
|
||||
onQueueClick={handleQueueClick}
|
||||
onPause={!isPending ? handlePause : undefined}
|
||||
onResume={!isPending ? handleResume : undefined}
|
||||
/>
|
||||
|
||||
<div className="queue-dashboard-footer">
|
||||
Auto-refreshes every {refreshInterval / 1000}s · Last updated:{' '}
|
||||
{new Date().toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
182
admin/frontend/src/pages/QueueDetailPage.tsx
Normal file
182
admin/frontend/src/pages/QueueDetailPage.tsx
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
import { useCallback, useMemo } from 'react';
|
||||
import { useParams, Link } from '@lilith/ui-router';
|
||||
|
||||
import { useQueueMetrics } from '../hooks/useQueueMetrics';
|
||||
import { useQueueControl } from '../hooks/useQueueControl';
|
||||
import { useBulkJobActions } from '../hooks/useBulkJobActions';
|
||||
|
||||
import { JobsTable } from '../components/JobsTable';
|
||||
import { BulkActionsBar } from '../components/BulkActionsBar';
|
||||
|
||||
import type { QueueStats } from '../types';
|
||||
|
||||
export interface QueueDetailPageProps {
|
||||
/** Base API URL for queue endpoints */
|
||||
apiUrl?: string;
|
||||
/** Custom class name */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detail page for a specific queue showing jobs with filtering.
|
||||
*
|
||||
* Uses react-router-dom useParams to get queueName from URL.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Route path="/queues/:queueName" element={<QueueDetailPage apiUrl="/api/admin/queues" />} />
|
||||
* ```
|
||||
*/
|
||||
export function QueueDetailPage({
|
||||
apiUrl = '/api/admin/queues',
|
||||
className = '',
|
||||
}: QueueDetailPageProps) {
|
||||
const { queueName } = useParams<{ queueName: string }>();
|
||||
|
||||
// Queries
|
||||
const { metrics, refetch } = useQueueMetrics({
|
||||
apiUrl,
|
||||
refreshInterval: 5000,
|
||||
enabled: !!queueName,
|
||||
});
|
||||
|
||||
// Find current queue stats from metrics
|
||||
const queueStats: QueueStats | null = useMemo(() => {
|
||||
const found = metrics.find((m) => m.name === queueName);
|
||||
if (!found) return null;
|
||||
return {
|
||||
name: found.name,
|
||||
waiting: found.counts.waiting,
|
||||
active: found.counts.active,
|
||||
completed: found.counts.completed,
|
||||
failed: found.counts.failed,
|
||||
delayed: found.counts.delayed,
|
||||
paused: found.isPaused,
|
||||
};
|
||||
}, [metrics, queueName]);
|
||||
|
||||
// Mutations
|
||||
const { retryJob } = useQueueControl({
|
||||
apiUrl,
|
||||
onSuccess: () => {
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
|
||||
const { retryAllFailed: bulkRetryAllFailed, cancelPending, cleanByAge } = useBulkJobActions({
|
||||
apiUrl,
|
||||
});
|
||||
|
||||
// Bulk action handlers
|
||||
const handleRetryAllFailed = useCallback(async () => {
|
||||
if (queueName) {
|
||||
await bulkRetryAllFailed.mutateAsync({ queueName });
|
||||
}
|
||||
}, [queueName, bulkRetryAllFailed]);
|
||||
|
||||
const handleCancelPending = useCallback(async () => {
|
||||
if (queueName) {
|
||||
await cancelPending.mutateAsync({ queueName });
|
||||
}
|
||||
}, [queueName, cancelPending]);
|
||||
|
||||
const handleCleanCompleted = useCallback(
|
||||
async (maxAgeMs: number) => {
|
||||
if (queueName) {
|
||||
await cleanByAge.mutateAsync({
|
||||
queueName,
|
||||
options: { status: 'completed', maxAgeMs },
|
||||
});
|
||||
}
|
||||
},
|
||||
[queueName, cleanByAge]
|
||||
);
|
||||
|
||||
const handleCleanFailed = useCallback(
|
||||
async (maxAgeMs: number) => {
|
||||
if (queueName) {
|
||||
await cleanByAge.mutateAsync({
|
||||
queueName,
|
||||
options: { status: 'failed', maxAgeMs },
|
||||
});
|
||||
}
|
||||
},
|
||||
[queueName, cleanByAge]
|
||||
);
|
||||
|
||||
const handleRetryJob = useCallback(
|
||||
(jobId: string) => {
|
||||
if (queueName) {
|
||||
retryJob({ queueName, jobId });
|
||||
}
|
||||
},
|
||||
[queueName, retryJob]
|
||||
);
|
||||
|
||||
if (!queueName) {
|
||||
return (
|
||||
<div className={`queue-detail-page ${className}`}>
|
||||
<div className="queue-detail-empty">
|
||||
<p>No queue selected</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`queue-detail-page ${className}`}>
|
||||
{/* Breadcrumb */}
|
||||
<nav className="queue-detail-breadcrumb">
|
||||
<Link to="/queues" className="queue-breadcrumb-link">
|
||||
Queues
|
||||
</Link>
|
||||
<span className="queue-breadcrumb-separator">/</span>
|
||||
<span className="queue-breadcrumb-current">{queueName}</span>
|
||||
</nav>
|
||||
|
||||
{/* Header */}
|
||||
<div className="queue-detail-header">
|
||||
<div className="queue-detail-title-section">
|
||||
<h1 className="queue-detail-title">{queueName}</h1>
|
||||
{queueStats && (
|
||||
<p className="queue-detail-status">
|
||||
<span
|
||||
className={
|
||||
queueStats.paused ? 'queue-status-warning' : 'queue-status-success'
|
||||
}
|
||||
>
|
||||
{queueStats.paused ? 'Paused' : 'Active'}
|
||||
</span>
|
||||
{' · '}
|
||||
{queueStats.waiting + queueStats.active + queueStats.delayed} pending jobs
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bulk Actions */}
|
||||
{queueStats && (
|
||||
<div className="queue-detail-bulk-actions">
|
||||
<BulkActionsBar
|
||||
queueName={queueName}
|
||||
failedCount={queueStats.failed}
|
||||
pendingCount={queueStats.waiting + queueStats.delayed}
|
||||
onRetryAllFailed={handleRetryAllFailed}
|
||||
onCancelPending={handleCancelPending}
|
||||
onCleanCompleted={handleCleanCompleted}
|
||||
onCleanFailed={handleCleanFailed}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Jobs Table (includes its own filters and pagination) */}
|
||||
<div className="queue-detail-table">
|
||||
<JobsTable
|
||||
queueName={queueName}
|
||||
apiUrl={apiUrl}
|
||||
onRetryJob={handleRetryJob}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
admin/frontend/src/pages/index.ts
Normal file
9
admin/frontend/src/pages/index.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Queue Admin Page Components
|
||||
*/
|
||||
|
||||
export { QueueDashboardPage } from './QueueDashboardPage';
|
||||
export type { QueueDashboardPageProps } from './QueueDashboardPage';
|
||||
|
||||
export { QueueDetailPage } from './QueueDetailPage';
|
||||
export type { QueueDetailPageProps } from './QueueDetailPage';
|
||||
|
|
@ -216,3 +216,53 @@ export interface RetryAllFailedOptions {
|
|||
queueName: string;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Queue Stats Types (for dashboard pages)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Per-queue statistics for dashboard display.
|
||||
*/
|
||||
export interface QueueStats {
|
||||
name: string;
|
||||
waiting: number;
|
||||
active: number;
|
||||
completed: number;
|
||||
failed: number;
|
||||
delayed: number;
|
||||
paused: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregated queue summary with totals.
|
||||
*/
|
||||
export interface QueueSummaryStats {
|
||||
queues: QueueStats[];
|
||||
totalWaiting: number;
|
||||
totalActive: number;
|
||||
totalCompleted: number;
|
||||
totalFailed: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Job status filter type.
|
||||
*/
|
||||
export type JobStatus = 'waiting' | 'active' | 'completed' | 'failed' | 'delayed';
|
||||
|
||||
/**
|
||||
* Service configuration for filtering.
|
||||
*/
|
||||
export interface ServiceInfo {
|
||||
value: string;
|
||||
label: string;
|
||||
queueName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for queue admin pages.
|
||||
*/
|
||||
export interface QueueAdminConfig {
|
||||
apiUrl: string;
|
||||
refreshInterval?: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"jsx": "react"
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "src/**/*.spec.ts", "src/**/*.spec.tsx", "src/**/*.test.ts", "src/**/*.test.tsx", "src/test/**"]
|
||||
|
|
|
|||
3
admin/frontend/tsup.config.ts
Normal file
3
admin/frontend/tsup.config.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { createReactLibraryConfig } from '@lilith/configs/tsup/react';
|
||||
|
||||
export default createReactLibraryConfig();
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# @queue/bull-adapter
|
||||
# @lilith/queue/bull-adapter
|
||||
|
||||
Bull/BullMQ queue adapter with NestJS module support. Provides a unified interface for queue operations with full TypeScript support.
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ Bull/BullMQ queue adapter with NestJS module support. Provides a unified interfa
|
|||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm add @queue/bull-adapter bullmq ioredis
|
||||
pnpm add @lilith/queue bullmq ioredis
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
|
@ -26,7 +26,7 @@ pnpm add @queue/bull-adapter bullmq ioredis
|
|||
|
||||
```typescript
|
||||
import { Module } from '@nestjs/common';
|
||||
import { QueueModule } from '@queue/bull-adapter';
|
||||
import { QueueModule } from '@lilith/queue/bull-adapter';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -51,7 +51,7 @@ export class AppModule {}
|
|||
```typescript
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { QueueModule } from '@queue/bull-adapter';
|
||||
import { QueueModule } from '@lilith/queue/bull-adapter';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -77,7 +77,7 @@ export class AppModule {}
|
|||
|
||||
```typescript
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BaseQueueService, JobOptions } from '@queue/bull-adapter';
|
||||
import { BaseQueueService, JobOptions } from '@lilith/queue/bull-adapter';
|
||||
|
||||
interface EmailPayload {
|
||||
to: string;
|
||||
|
|
@ -294,7 +294,7 @@ Create a worker to process jobs:
|
|||
|
||||
```typescript
|
||||
import { Worker, Job } from 'bullmq';
|
||||
import { JobData } from '@queue/bull-adapter';
|
||||
import { JobData } from '@lilith/queue/bull-adapter';
|
||||
|
||||
const worker = new Worker(
|
||||
'main',
|
||||
|
|
@ -337,7 +337,7 @@ import {
|
|||
DEFAULT_JOB_TIMEOUT, // 300000 (5 minutes)
|
||||
DEFAULT_CONCURRENCY, // 1
|
||||
DEFAULT_REDIS_CONFIG, // { host: 'localhost', port: 6379 }
|
||||
} from '@queue/bull-adapter';
|
||||
} from '@lilith/queue/bull-adapter';
|
||||
```
|
||||
|
||||
## TypeScript Support
|
||||
|
|
|
|||
11
bull-adapter/package.json
Normal file
11
bull-adapter/package.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"type": "module",
|
||||
"peerDependencies": {
|
||||
"@lilith/queue": "workspace:*",
|
||||
"bullmq": "^5.0.0",
|
||||
"ioredis": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lilith/configs": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,5 +5,5 @@
|
|||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
|
||||
}
|
||||
|
|
|
|||
3
bull-adapter/tsup.config.ts
Normal file
3
bull-adapter/tsup.config.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { createLibraryConfig } from '@lilith/configs/tsup/library';
|
||||
|
||||
export default createLibraryConfig();
|
||||
143
cli/.forgejo/workflows/publish.yml
Normal file
143
cli/.forgejo/workflows/publish.yml
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
# =============================================================================
|
||||
# Forgejo Actions Workflow - Config Package Publishing
|
||||
# =============================================================================
|
||||
# Simplified template for config-only packages (no build step required)
|
||||
#
|
||||
# Use for:
|
||||
# - ESLint configurations
|
||||
# - TypeScript configurations
|
||||
# - Prettier configurations
|
||||
# - JSON/YAML config packages
|
||||
# - Any package where source === dist
|
||||
#
|
||||
# Features:
|
||||
# - JSON/YAML validation
|
||||
# - Version existence check
|
||||
# - No build step (publishes source directly)
|
||||
# - Fast CI (< 1 minute typical)
|
||||
#
|
||||
# Usage:
|
||||
# 1. Copy to: <package>/.forgejo/workflows/publish.yml
|
||||
# 2. Ensure package.json has metadata:
|
||||
# "_": { "registry": "forgejo", "publish": true, "build": false }
|
||||
# 3. Commit and push to main/master
|
||||
#
|
||||
# Secrets required:
|
||||
# - NPM_TOKEN: Forgejo npm registry token
|
||||
# =============================================================================
|
||||
|
||||
name: Validate and Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODE_VERSION: '22'
|
||||
|
||||
jobs:
|
||||
validate-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Configure npm for Forgejo registry
|
||||
run: |
|
||||
echo "@lilith:registry=https://forge.nasty.sh/api/packages/lilith/npm/" > .npmrc
|
||||
echo "//forge.nasty.sh/api/packages/lilith/npm/:_authToken=\${NPM_TOKEN}" >> .npmrc
|
||||
echo "strict-ssl=false" >> .npmrc
|
||||
echo "✓ Configured Forgejo registry"
|
||||
|
||||
- name: Validate configuration files
|
||||
run: |
|
||||
echo "=== Validating configuration files ==="
|
||||
|
||||
# Validate JSON files
|
||||
for f in *.json; do
|
||||
if [ -f "$f" ]; then
|
||||
if node -e "JSON.parse(require('fs').readFileSync('$f', 'utf8'))" 2>&1; then
|
||||
echo "✓ Valid JSON: $f"
|
||||
else
|
||||
echo "✗ Invalid JSON: $f"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate YAML files
|
||||
for f in *.yaml *.yml; do
|
||||
if [ -f "$f" ] && [ "$f" != "publish.yml" ] && [ "$f" != "publish-config.yml" ]; then
|
||||
if node -e "require('js-yaml').load(require('fs').readFileSync('$f', 'utf8'))" 2>/dev/null; then
|
||||
echo "✓ Valid YAML: $f"
|
||||
else
|
||||
echo "⚠ Could not validate YAML: $f (js-yaml not available, skipping)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate package.json specifically
|
||||
if [ -f "package.json" ]; then
|
||||
if node -p "JSON.stringify(require('./package.json'), null, 2)" > /dev/null 2>&1; then
|
||||
echo "✓ Valid package.json"
|
||||
else
|
||||
echo "✗ Invalid package.json"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "✓ All configuration files valid"
|
||||
|
||||
- name: Check and Publish
|
||||
run: |
|
||||
echo "=== Publish Configuration ==="
|
||||
|
||||
pkg_name=$(node -p "require('./package.json').name")
|
||||
pkg_version=$(node -p "require('./package.json').version")
|
||||
should_publish=$(node -p "require('./package.json')._?.publish === true")
|
||||
registry=$(node -p "require('./package.json')._?.registry || 'none'")
|
||||
|
||||
echo ""
|
||||
echo "Package: $pkg_name@$pkg_version"
|
||||
echo " Publish: $should_publish"
|
||||
echo " Registry: $registry"
|
||||
echo ""
|
||||
|
||||
# Check registry configuration
|
||||
if [ "$registry" != "forgejo" ]; then
|
||||
echo "⊘ Skipping: registry is not 'forgejo' (got: $registry)"
|
||||
echo " To enable publishing, add to package.json:"
|
||||
echo " \"_\": { \"registry\": \"forgejo\", \"publish\": true }"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Publish if configured
|
||||
if [ "$should_publish" = "true" ]; then
|
||||
echo "=== Checking if version already published ==="
|
||||
if npm view "$pkg_name@$pkg_version" version 2>/dev/null; then
|
||||
echo "✓ Version $pkg_version already published to registry"
|
||||
echo " No action needed"
|
||||
else
|
||||
echo "→ Version $pkg_version not found in registry"
|
||||
echo ""
|
||||
echo "=== Publishing to Forgejo registry ==="
|
||||
if npm publish --access public --no-git-checks 2>&1; then
|
||||
echo ""
|
||||
echo "✓ Successfully published $pkg_name@$pkg_version"
|
||||
echo " Registry: https://forge.nasty.sh/lilith/-/packages/npm/$pkg_name"
|
||||
else
|
||||
echo "✗ Publish failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "⊘ Publish skipped (publish: false)"
|
||||
fi
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
"name": "@lilith/queue-cli",
|
||||
"version": "0.1.0",
|
||||
"description": "CLI tools for managing BullMQ queues",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"bin": {
|
||||
|
|
@ -12,19 +13,18 @@
|
|||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prepublishOnly": "pnpm build"
|
||||
"prepublishOnly": "bun run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"bullmq": "^5.34.0",
|
||||
"ioredis": "^5.4.0",
|
||||
"commander": "^12.0.0"
|
||||
"bullmq": "^5.66.4",
|
||||
"commander": "^12.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.7.0"
|
||||
"@types/node": "^22.19.5",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "http://forge.nasty.sh/api/packages/lilith/npm/"
|
||||
"registry": "http://forge.black.lan/api/packages/lilith/npm/"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Queue, Job } from 'bullmq';
|
||||
import IORedis from 'ioredis';
|
||||
|
||||
export type JobState = 'waiting' | 'active' | 'completed' | 'failed' | 'delayed' | 'paused';
|
||||
|
||||
|
|
@ -28,7 +27,6 @@ export interface JobInfo {
|
|||
}
|
||||
|
||||
export class QueueClient {
|
||||
private connection: IORedis;
|
||||
private queue: Queue;
|
||||
readonly queueName: string;
|
||||
readonly redisUrl: string;
|
||||
|
|
@ -37,12 +35,13 @@ export class QueueClient {
|
|||
this.queueName = options.queueName;
|
||||
this.redisUrl = options.redisUrl || process.env.REDIS_URL || 'redis://localhost:6379';
|
||||
|
||||
this.connection = new IORedis(this.redisUrl, {
|
||||
maxRetriesPerRequest: null,
|
||||
enableReadyCheck: false,
|
||||
this.queue = new Queue(this.queueName, {
|
||||
connection: {
|
||||
url: this.redisUrl,
|
||||
maxRetriesPerRequest: null,
|
||||
enableReadyCheck: false,
|
||||
},
|
||||
});
|
||||
|
||||
this.queue = new Queue(this.queueName, { connection: this.connection });
|
||||
}
|
||||
|
||||
async getJobCounts(): Promise<JobCounts> {
|
||||
|
|
@ -164,6 +163,5 @@ export class QueueClient {
|
|||
|
||||
async close(): Promise<void> {
|
||||
await this.queue.close();
|
||||
await this.connection.quit();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
core/package.json
Normal file
6
core/package.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@lilith/configs": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,5 +5,5 @@
|
|||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
|
||||
}
|
||||
|
|
|
|||
10
core/tsup.config.ts
Normal file
10
core/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { createLibraryConfig } from '@lilith/configs/tsup/library';
|
||||
|
||||
export default createLibraryConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
'types/index': 'src/types/index.ts',
|
||||
'constants/index': 'src/constants/index.ts',
|
||||
'utils/index': 'src/utils/index.ts',
|
||||
},
|
||||
});
|
||||
3
eslint.config.js
Normal file
3
eslint.config.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { createBaseConfig } from '@lilith/configs/eslint/base-flat';
|
||||
|
||||
export default createBaseConfig({ tsconfigRootDir: import.meta.dirname, tsconfigPath: './tsconfig.base.json' });
|
||||
13
ml/package.json
Normal file
13
ml/package.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"type": "module",
|
||||
"peerDependencies": {
|
||||
"@lilith/queue": "workspace:*",
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/bullmq": "^10.0.0 || ^11.0.0",
|
||||
"bullmq": "^5.0.0",
|
||||
"ioredis": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lilith/configs": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,5 +5,5 @@
|
|||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
|
||||
}
|
||||
|
|
|
|||
9
ml/tsup.config.ts
Normal file
9
ml/tsup.config.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { createLibraryConfig } from '@lilith/configs/tsup/library';
|
||||
|
||||
export default createLibraryConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
'strategies/index': 'src/strategies/index.ts',
|
||||
'processors/index': 'src/processors/index.ts',
|
||||
},
|
||||
});
|
||||
23
nestjs/package.json
Normal file
23
nestjs/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"type": "module",
|
||||
"peerDependencies": {
|
||||
"@lilith/queue": "workspace:*",
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/bullmq": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/schedule": "^4.0.0 || ^5.0.0 || ^6.0.0",
|
||||
"@nestjs/typeorm": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/websockets": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/platform-socket.io": "^10.0.0 || ^11.0.0",
|
||||
"bullmq": "^5.0.0",
|
||||
"ioredis": "^5.0.0",
|
||||
"cron": "^3.0.0",
|
||||
"class-transformer": "^0.5.0",
|
||||
"class-validator": "^0.14.0",
|
||||
"typeorm": "^0.3.0",
|
||||
"rxjs": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lilith/configs": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,5 +5,5 @@
|
|||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
|
||||
}
|
||||
|
|
|
|||
10
nestjs/tsup.config.ts
Normal file
10
nestjs/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { createLibraryConfig } from '@lilith/configs/tsup/library';
|
||||
|
||||
export default createLibraryConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
'base/index': 'src/base/index.ts',
|
||||
'decorators/index': 'src/decorators/index.ts',
|
||||
'services/index': 'src/services/index.ts',
|
||||
},
|
||||
});
|
||||
106
package.json
106
package.json
|
|
@ -1,8 +1,14 @@
|
|||
{
|
||||
"name": "@lilith/queue",
|
||||
"version": "1.2.2",
|
||||
"version": "1.3.35",
|
||||
"description": "Job queue ecosystem: core types, NestJS integration, ML batching, reporting, and admin dashboard",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./core/dist/index.d.ts",
|
||||
"import": "./core/dist/index.js",
|
||||
"require": "./core/dist/index.js"
|
||||
},
|
||||
"./core": {
|
||||
"types": "./core/dist/index.d.ts",
|
||||
"import": "./core/dist/index.js",
|
||||
|
|
@ -94,27 +100,27 @@
|
|||
"admin/frontend/dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pnpm run build:core && pnpm run build:nestjs && pnpm run build:ml && pnpm run build:reporting && pnpm run build:bull-adapter && pnpm run build:admin",
|
||||
"build:core": "tsc -p core/tsconfig.json",
|
||||
"build:nestjs": "tsc -p nestjs/tsconfig.json",
|
||||
"build:ml": "tsc -p ml/tsconfig.json",
|
||||
"build:reporting": "tsc -p reporting/tsconfig.json",
|
||||
"build:bull-adapter": "tsc -p bull-adapter/tsconfig.json",
|
||||
"build:admin": "tsc -p admin/backend/tsconfig.json && tsc -p admin/frontend/tsconfig.json",
|
||||
"dev": "pnpm run --parallel dev:*",
|
||||
"dev:core": "tsc -p core/tsconfig.json --watch",
|
||||
"dev:nestjs": "tsc -p nestjs/tsconfig.json --watch",
|
||||
"dev:ml": "tsc -p ml/tsconfig.json --watch",
|
||||
"dev:reporting": "tsc -p reporting/tsconfig.json --watch",
|
||||
"dev:bull-adapter": "tsc -p bull-adapter/tsconfig.json --watch",
|
||||
"dev:admin": "tsc -p admin/backend/tsconfig.json --watch & tsc -p admin/frontend/tsconfig.json --watch",
|
||||
"build": "bun run build:core && bun run build:nestjs && bun run build:ml && bun run build:reporting && bun run build:bull-adapter && bun run build:admin",
|
||||
"build:core": "cd core && tsup",
|
||||
"build:nestjs": "cd nestjs && tsup",
|
||||
"build:ml": "cd ml && tsup",
|
||||
"build:reporting": "cd reporting && tsup",
|
||||
"build:bull-adapter": "cd bull-adapter && tsup",
|
||||
"build:admin": "cd admin/backend && tsup && cd ../frontend && tsup",
|
||||
"dev": "bun run --parallel dev:*",
|
||||
"dev:core": "cd core && tsup --watch",
|
||||
"dev:nestjs": "cd nestjs && tsup --watch",
|
||||
"dev:ml": "cd ml && tsup --watch",
|
||||
"dev:reporting": "cd reporting && tsup --watch",
|
||||
"dev:bull-adapter": "cd bull-adapter && tsup --watch",
|
||||
"dev:admin": "cd admin/backend && tsup --watch & cd admin/frontend && tsup --watch",
|
||||
"test": "vitest",
|
||||
"test:e2e": "vitest run --config e2e/vitest.config.ts",
|
||||
"lint": "eslint . --fix",
|
||||
"lint:check": "eslint .",
|
||||
"typecheck": "pnpm run build",
|
||||
"typecheck": "tsc --noEmit -p core/tsconfig.json && tsc --noEmit -p nestjs/tsconfig.json && tsc --noEmit -p ml/tsconfig.json && tsc --noEmit -p reporting/tsconfig.json && tsc --noEmit -p bull-adapter/tsconfig.json && tsc --noEmit -p admin/backend/tsconfig.json && tsc --noEmit -p admin/frontend/tsconfig.json",
|
||||
"clean": "rm -rf core/dist nestjs/dist ml/dist reporting/dist bull-adapter/dist admin/backend/dist admin/frontend/dist",
|
||||
"prepublishOnly": "pnpm run build"
|
||||
"prepublishOnly": "bun run build"
|
||||
},
|
||||
"keywords": [
|
||||
"queue",
|
||||
|
|
@ -129,13 +135,17 @@
|
|||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.59.0",
|
||||
"bullmq": "^5.0.0",
|
||||
"@tanstack/react-query": "^5.90.16",
|
||||
"bullmq": "^5.66.4",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.3",
|
||||
"typeorm": "^0.3.0"
|
||||
"typeorm": "^0.3.28"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@lilith/ui-data": "workspace:*",
|
||||
"@lilith/ui-feedback": "workspace:*",
|
||||
"@lilith/ui-layout": "workspace:*",
|
||||
"@lilith/ui-primitives": "workspace:*",
|
||||
"@nestjs/bullmq": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
||||
|
|
@ -145,9 +155,12 @@
|
|||
"@nestjs/websockets": "^10.0.0 || ^11.0.0",
|
||||
"cron": "^3.0.0",
|
||||
"ioredis": "^5.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"@lilith/ui-router": "^1.3.0",
|
||||
"react-router-dom": "^6.0.0 || ^7.0.0",
|
||||
"socket.io": "^4.0.0",
|
||||
"socket.io-client": "^4.7.0"
|
||||
"socket.io-client": "^4.7.0",
|
||||
"styled-components": "^6.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nestjs/bullmq": {
|
||||
|
|
@ -171,6 +184,18 @@
|
|||
"@nestjs/websockets": {
|
||||
"optional": true
|
||||
},
|
||||
"@lilith/ui-primitives": {
|
||||
"optional": true
|
||||
},
|
||||
"@lilith/ui-data": {
|
||||
"optional": true
|
||||
},
|
||||
"@lilith/ui-feedback": {
|
||||
"optional": true
|
||||
},
|
||||
"@lilith/ui-layout": {
|
||||
"optional": true
|
||||
},
|
||||
"cron": {
|
||||
"optional": true
|
||||
},
|
||||
|
|
@ -180,14 +205,29 @@
|
|||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-router-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"socket.io": {
|
||||
"optional": true
|
||||
},
|
||||
"socket.io-client": {
|
||||
"optional": true
|
||||
},
|
||||
"@lilith/ui-router": {
|
||||
"optional": true
|
||||
},
|
||||
"styled-components": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lilith/ui-data": "workspace:*",
|
||||
"@lilith/ui-router": "^1.3.0",
|
||||
"tsup": "^8.5.1",
|
||||
"@lilith/ui-feedback": "workspace:*",
|
||||
"@lilith/ui-layout": "workspace:*",
|
||||
"@lilith/ui-primitives": "workspace:*",
|
||||
"@nestjs/bullmq": "^11.0.4",
|
||||
"@nestjs/common": "^11.1.11",
|
||||
"@nestjs/core": "^11.1.11",
|
||||
|
|
@ -198,26 +238,28 @@
|
|||
"@nestjs/websockets": "^11.1.11",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.1",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/node": "^20.19.28",
|
||||
"@types/react": "^19.2.8",
|
||||
"@vitest/coverage-v8": "^4.0.16",
|
||||
"cron": "^3.0.0",
|
||||
"happy-dom": "^12.0.0",
|
||||
"ioredis": "^5.0.0",
|
||||
"react": "^18.3.0",
|
||||
"cron": "^3.5.0",
|
||||
"happy-dom": "^12.10.3",
|
||||
"ioredis": "^5.9.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.12.0",
|
||||
"reflect-metadata": "^0.1.14",
|
||||
"rxjs": "^7.8.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"socket.io": "^4.8.3",
|
||||
"socket.io-client": "^4.7.0",
|
||||
"typescript": "^5.0.0",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"styled-components": "^6.3.5",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://forge.nasty.sh/api/packages/lilith/npm/"
|
||||
"registry": "http://forge.black.lan/api/packages/lilith/npm/"
|
||||
},
|
||||
"_": {
|
||||
"registry": "forgejo",
|
||||
|
|
|
|||
12
reporting/package.json
Normal file
12
reporting/package.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"type": "module",
|
||||
"peerDependencies": {
|
||||
"@lilith/queue": "workspace:*",
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/typeorm": "^10.0.0 || ^11.0.0",
|
||||
"typeorm": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lilith/configs": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
@ -6,5 +6,5 @@
|
|||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
|
||||
}
|
||||
|
|
|
|||
3
reporting/tsup.config.ts
Normal file
3
reporting/tsup.config.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { createLibraryConfig } from '@lilith/configs/tsup/library';
|
||||
|
||||
export default createLibraryConfig();
|
||||
|
|
@ -3,15 +3,15 @@
|
|||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"noImplicitThis": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"alwaysStrict": true,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue