Files
wehub-resource-sync d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

146 lines
4.8 KiB
TypeScript

import { createParser, parseAsArrayOf, parseAsString, parseAsStringLiteral } from 'nuqs/server'
import {
CORE_TRIGGER_TYPES,
type LogLevel,
type TimeRange,
type TriggerType,
} from '@/stores/logs/filters/types'
/**
* Co-located, typed URL query-param definitions for the logs feature. The
* client hook (`useLogFilters`) consumes this typed param definition as the
* single source of truth.
*
* The encoding here intentionally preserves the exact wire format the logs page
* shipped before nuqs: `timeRange` uses kebab tokens, `level` / `workflowIds` /
* `folderIds` / `triggers` are comma-joined, and `search` is trimmed.
*/
const DEFAULT_TIME_RANGE: TimeRange = 'All time'
/** Maps a {@link TimeRange} label to its stable URL token and back. */
const TIME_RANGE_TO_TOKEN: Record<TimeRange, string> = {
'All time': 'all-time',
'Past 30 minutes': 'past-30-minutes',
'Past hour': 'past-hour',
'Past 6 hours': 'past-6-hours',
'Past 12 hours': 'past-12-hours',
'Past 24 hours': 'past-24-hours',
'Past 3 days': 'past-3-days',
'Past 7 days': 'past-7-days',
'Past 14 days': 'past-14-days',
'Past 30 days': 'past-30-days',
'Custom range': 'custom',
}
const TOKEN_TO_TIME_RANGE: Record<string, TimeRange> = Object.fromEntries(
Object.entries(TIME_RANGE_TO_TOKEN).map(([label, token]) => [token, label as TimeRange])
) as Record<string, TimeRange>
/**
* Parser for the `timeRange` param. Serializes labels to kebab tokens and
* tolerantly maps unknown tokens back to the default ("All time").
*/
export const parseAsTimeRange = createParser<TimeRange>({
parse(value) {
return TOKEN_TO_TIME_RANGE[value] ?? DEFAULT_TIME_RANGE
},
serialize(value) {
return TIME_RANGE_TO_TOKEN[value] ?? 'all-time'
},
})
const VALID_LEVELS = ['error', 'info', 'running', 'pending'] as const
/**
* Parser for the `level` param. `level` is a comma-joined list of statuses on
* the wire but is surfaced as a single `LogLevel` value ("all", a single status,
* or a comma-joined string) to match the existing store contract.
*/
export const parseAsLogLevel = createParser<LogLevel>({
parse(value) {
const levels = value
.split(',')
.filter((l): l is (typeof VALID_LEVELS)[number] =>
(VALID_LEVELS as readonly string[]).includes(l)
)
if (levels.length === 0) return 'all'
if (levels.length === 1) return levels[0]
return levels.join(',') as LogLevel
},
serialize(value) {
return value
},
})
const CORE_TRIGGER_SET = new Set<string>(CORE_TRIGGER_TYPES)
/**
* Parser for the `triggers` param, restricted to known core trigger types.
* Surfaced as `TriggerType[]` to match the consumer contract — unknown tokens
* are dropped (mirrors the prior `parseTriggerArrayFromURL` behavior).
*/
export const parseAsTriggers = createParser<TriggerType[]>({
parse(value) {
const triggers = value.split(',').filter((t): t is TriggerType => CORE_TRIGGER_SET.has(t))
return triggers
},
serialize(value) {
return value.join(',')
},
eq(a, b) {
return a.length === b.length && a.every((v, i) => v === b[i])
},
}).withDefault([])
/**
* The nuqs parser map for every URL-synced logs filter. `clearOnDefault` keeps
* the URL clean (params drop out when they hold their default value) and
* `history: 'replace'` matches the prior `history.replaceState` behavior so
* filter changes don't pollute the browser back stack.
*/
export const logFilterParsers = {
timeRange: parseAsTimeRange.withDefault(DEFAULT_TIME_RANGE),
startDate: parseAsString,
endDate: parseAsString,
level: parseAsLogLevel.withDefault('all'),
workflowIds: parseAsArrayOf(parseAsString).withDefault([]),
folderIds: parseAsArrayOf(parseAsString).withDefault([]),
triggers: parseAsTriggers,
search: parseAsString.withDefault(''),
} as const
/** Shared nuqs options for the logs filters: clean URLs, no back-stack churn. */
export const logFilterUrlKeys = {
history: 'replace',
clearOnDefault: true,
} as const
/**
* Read-only deep link to a specific execution. Resolves to a log row and opens
* the details sidebar on load. Intentionally NOT stripped — the link stays
* shareable — so it carries no `clearOnDefault`/`history` options here.
*/
export const executionIdParam = {
key: 'executionId',
parser: parseAsString,
} as const
const LOG_DETAILS_TABS = ['overview', 'trace'] as const
/**
* Active tab of the log-details sidebar (`overview` / `trace`). Deep-linkable so
* a shared link can land on the trace view; `replace` keeps it off the back
* stack and `clearOnDefault` drops it from the URL when on the default tab.
*/
export const logDetailsTabParam = {
key: 'tab',
parser: parseAsStringLiteral(LOG_DETAILS_TABS).withDefault('overview'),
} as const
/** Tab change is view-state, not a destination: replace, clean URL on default. */
export const logDetailsTabUrlKeys = {
history: 'replace',
clearOnDefault: true,
} as const