@ontrails/core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +849 -0
- package/README.md +190 -0
- package/package.json +36 -0
- package/src/activation-provenance.ts +116 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-derivation.ts +227 -0
- package/src/activation-source.ts +93 -0
- package/src/blob-ref.ts +90 -0
- package/src/branded.ts +135 -0
- package/src/collections.ts +99 -0
- package/src/compose-batch.ts +69 -0
- package/src/compose-schema.ts +36 -0
- package/src/context.ts +66 -0
- package/src/derive.ts +485 -0
- package/src/detours.ts +8 -0
- package/src/diagnostics.ts +21 -0
- package/src/draft.ts +350 -0
- package/src/entity.ts +346 -0
- package/src/error-rendering.ts +87 -0
- package/src/errors.ts +483 -0
- package/src/execute.ts +1577 -0
- package/src/fetch.ts +138 -0
- package/src/fire.ts +1172 -0
- package/src/glob.ts +81 -0
- package/src/guards.ts +37 -0
- package/src/index.ts +704 -0
- package/src/internal/fork-ctx.ts +69 -0
- package/src/layer-field-rendering.ts +193 -0
- package/src/layer.ts +81 -0
- package/src/observe.ts +361 -0
- package/src/path-scope.ts +66 -0
- package/src/path-security.ts +98 -0
- package/src/patterns/bulk.ts +16 -0
- package/src/patterns/change.ts +12 -0
- package/src/patterns/date-range.ts +12 -0
- package/src/patterns/index.ts +8 -0
- package/src/patterns/pagination.ts +22 -0
- package/src/patterns/progress.ts +13 -0
- package/src/patterns/sorting.ts +14 -0
- package/src/patterns/status.ts +11 -0
- package/src/patterns/timestamps.ts +12 -0
- package/src/permits.ts +12 -0
- package/src/queue.ts +163 -0
- package/src/redaction/index.ts +3 -0
- package/src/redaction/patterns.ts +50 -0
- package/src/redaction/redactor.ts +178 -0
- package/src/resilience.ts +234 -0
- package/src/resource-config.ts +804 -0
- package/src/resource.ts +194 -0
- package/src/result.ts +212 -0
- package/src/run.ts +76 -0
- package/src/runtime-builtins.ts +69 -0
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +326 -0
- package/src/serialization.ts +265 -0
- package/src/sha256.ts +136 -0
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +111 -0
- package/src/signal.ts +104 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/structured-examples.ts +248 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +101 -0
- package/src/surface-overlay.ts +694 -0
- package/src/surface-versioning.ts +42 -0
- package/src/topo.ts +835 -0
- package/src/tracing.ts +346 -0
- package/src/trail-id-glob.ts +15 -0
- package/src/trail.ts +1351 -0
- package/src/trails/derive-trail.ts +835 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +152 -0
- package/src/trails-db.ts +212 -0
- package/src/transport-error-map.ts +163 -0
- package/src/type-utils.ts +87 -0
- package/src/types.ts +300 -0
- package/src/validate-established-topo.ts +73 -0
- package/src/validate-topo.ts +725 -0
- package/src/validation.ts +330 -0
- package/src/version-marker.ts +716 -0
- package/src/version-resolution.ts +308 -0
- package/src/version-runtime.ts +120 -0
- package/src/webhook.ts +461 -0
- package/src/workspace.ts +244 -0
- package/src/zod-wrappers.ts +72 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { matchesAnyGlob, matchesGlob } from './glob.js';
|
|
4
|
+
|
|
5
|
+
declare const pathGlobBrand: unique symbol;
|
|
6
|
+
|
|
7
|
+
export type PathGlob = string & {
|
|
8
|
+
readonly [pathGlobBrand]: 'PathGlob';
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const normalizePathScopePath = (value: string): string =>
|
|
12
|
+
value.replaceAll('\\', '/').replace(/^\.\//, '');
|
|
13
|
+
|
|
14
|
+
export const matchesPathGlob = (path: string, pattern: string): boolean =>
|
|
15
|
+
matchesGlob(normalizePathScopePath(path), normalizePathScopePath(pattern), {
|
|
16
|
+
separator: '/',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const matchesAnyPathGlob = (
|
|
20
|
+
path: string,
|
|
21
|
+
patterns: readonly string[] | undefined
|
|
22
|
+
): boolean =>
|
|
23
|
+
matchesAnyGlob(
|
|
24
|
+
normalizePathScopePath(path),
|
|
25
|
+
patterns?.map(normalizePathScopePath),
|
|
26
|
+
{ separator: '/' }
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const pathGlobArraySchema = z.array(z.string()).readonly();
|
|
30
|
+
|
|
31
|
+
export const pathScopeSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
exclude: pathGlobArraySchema.optional(),
|
|
34
|
+
extensions: z.array(z.string()).readonly().optional(),
|
|
35
|
+
include: pathGlobArraySchema.optional(),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
|
|
39
|
+
export type PathScope = z.output<typeof pathScopeSchema>;
|
|
40
|
+
|
|
41
|
+
export type ScanTargets = Pick<PathScope, 'exclude' | 'extensions'>;
|
|
42
|
+
|
|
43
|
+
const extensionOf = (path: string): string => {
|
|
44
|
+
const normalized = normalizePathScopePath(path);
|
|
45
|
+
const name = normalized.slice(normalized.lastIndexOf('/') + 1);
|
|
46
|
+
const dot = name.lastIndexOf('.');
|
|
47
|
+
return dot <= 0 ? '' : name.slice(dot);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const normalizeExtension = (extension: string): string =>
|
|
51
|
+
extension === '' || extension.startsWith('.') ? extension : `.${extension}`;
|
|
52
|
+
|
|
53
|
+
const includedByExtension = (
|
|
54
|
+
path: string,
|
|
55
|
+
extensions: readonly string[] | undefined
|
|
56
|
+
): boolean =>
|
|
57
|
+
extensions === undefined ||
|
|
58
|
+
extensions.length === 0 ||
|
|
59
|
+
extensions.map(normalizeExtension).includes(extensionOf(path));
|
|
60
|
+
|
|
61
|
+
export const includedByPathScope = (path: string, scope?: PathScope): boolean =>
|
|
62
|
+
(scope?.include === undefined ||
|
|
63
|
+
scope.include.length === 0 ||
|
|
64
|
+
matchesAnyPathGlob(path, scope.include)) &&
|
|
65
|
+
!matchesAnyPathGlob(path, scope?.exclude) &&
|
|
66
|
+
includedByExtension(path, scope?.extensions);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path security utilities for preventing path traversal attacks.
|
|
3
|
+
*
|
|
4
|
+
* All functions are runtime-agnostic (Node / Bun compatible).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { PermissionError } from './errors.js';
|
|
8
|
+
import { Result } from './result.js';
|
|
9
|
+
// Path security guards filesystem access on tooling paths: node:path
|
|
10
|
+
// loads lazily at first use so the core barrel's module graph stays
|
|
11
|
+
// execution-portable on runtimes without node: builtins (TRL-1198).
|
|
12
|
+
import { loadRuntimeBuiltin } from './runtime-builtins.js';
|
|
13
|
+
|
|
14
|
+
const nodePath = () => loadRuntimeBuiltin('node:path');
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Internal
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/** Returns true when `target` is equal to or a descendant of `base`. */
|
|
21
|
+
const isWithin = (base: string, target: string): boolean => {
|
|
22
|
+
const { isAbsolute, relative } = nodePath();
|
|
23
|
+
const rel = relative(base, target);
|
|
24
|
+
// Empty string means they are the same directory.
|
|
25
|
+
// A relative path starting with ".." means it escapes.
|
|
26
|
+
// An absolute path means a completely different tree.
|
|
27
|
+
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Public API
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolves `userPath` relative to `basePath` and ensures it stays within
|
|
36
|
+
* the base directory. Returns the resolved absolute path on success, or a
|
|
37
|
+
* `PermissionError` if the path escapes.
|
|
38
|
+
*
|
|
39
|
+
* Uses lexical path comparison (not `realpath`). Does not follow symlinks —
|
|
40
|
+
* if an attacker can create symlinks inside `basePath`, those could point
|
|
41
|
+
* outside the base. Use `realpath` before calling in symlink-sensitive environments.
|
|
42
|
+
*/
|
|
43
|
+
export const securePath = (
|
|
44
|
+
basePath: string,
|
|
45
|
+
userPath: string
|
|
46
|
+
): Result<string, PermissionError> => {
|
|
47
|
+
const { resolve } = nodePath();
|
|
48
|
+
const base = resolve(basePath);
|
|
49
|
+
const resolved = resolve(base, userPath);
|
|
50
|
+
|
|
51
|
+
if (!isWithin(base, resolved)) {
|
|
52
|
+
return Result.err(
|
|
53
|
+
new PermissionError(
|
|
54
|
+
`Path traversal detected: "${userPath}" escapes "${basePath}"`,
|
|
55
|
+
{
|
|
56
|
+
context: { basePath: base, resolved, userPath },
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Result.ok(resolved);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns `true` if `userPath` (resolved against `basePath`) stays within
|
|
67
|
+
* `basePath`.
|
|
68
|
+
*/
|
|
69
|
+
export const isPathSafe = (basePath: string, userPath: string): boolean => {
|
|
70
|
+
const { resolve } = nodePath();
|
|
71
|
+
const base = resolve(basePath);
|
|
72
|
+
const resolved = resolve(base, userPath);
|
|
73
|
+
return isWithin(base, resolved);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Joins multiple path segments, resolves them against `basePath`, and
|
|
78
|
+
* validates the result stays within the base directory.
|
|
79
|
+
*/
|
|
80
|
+
export const deriveSafePath = (
|
|
81
|
+
basePath: string,
|
|
82
|
+
...segments: string[]
|
|
83
|
+
): Result<string, PermissionError> => {
|
|
84
|
+
const { normalize, resolve } = nodePath();
|
|
85
|
+
const base = resolve(basePath);
|
|
86
|
+
const joined = resolve(base, ...segments.map((s) => normalize(s)));
|
|
87
|
+
|
|
88
|
+
if (!isWithin(base, joined)) {
|
|
89
|
+
return Result.err(
|
|
90
|
+
new PermissionError(
|
|
91
|
+
`Path traversal detected: segments escape "${basePath}"`,
|
|
92
|
+
{ context: { basePath: base, resolved: joined, segments } }
|
|
93
|
+
)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Result.ok(joined);
|
|
98
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk operation schema helpers for @ontrails/core/patterns
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
/** Bulk operation output wrapper for a given item schema. */
|
|
8
|
+
export const bulkOutput = <T>(itemSchema: z.ZodType<T>) =>
|
|
9
|
+
z.object({
|
|
10
|
+
errors: z
|
|
11
|
+
.array(z.object({ index: z.number(), message: z.string() }))
|
|
12
|
+
.optional(),
|
|
13
|
+
failed: z.number(),
|
|
14
|
+
items: z.array(itemSchema),
|
|
15
|
+
succeeded: z.number(),
|
|
16
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change-tracking schema helpers for @ontrails/core/patterns
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
/** Before/after change output for a given schema. */
|
|
8
|
+
export const changeOutput = <T>(schema: z.ZodType<T>) =>
|
|
9
|
+
z.object({
|
|
10
|
+
after: schema,
|
|
11
|
+
before: schema.optional(),
|
|
12
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date-range schema helpers for @ontrails/core/patterns
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
/** Optional since/until date-range fields. */
|
|
8
|
+
export const dateRangeFields = () =>
|
|
9
|
+
z.object({
|
|
10
|
+
since: z.string().optional(),
|
|
11
|
+
until: z.string().optional(),
|
|
12
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { paginationFields, paginatedOutput } from './pagination.js';
|
|
2
|
+
export { bulkOutput } from './bulk.js';
|
|
3
|
+
export { timestampFields } from './timestamps.js';
|
|
4
|
+
export { dateRangeFields } from './date-range.js';
|
|
5
|
+
export { sortFields } from './sorting.js';
|
|
6
|
+
export { statusFields } from './status.js';
|
|
7
|
+
export { changeOutput } from './change.js';
|
|
8
|
+
export { progressFields } from './progress.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination schema helpers for @ontrails/core/patterns
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
/** Common pagination input fields. */
|
|
8
|
+
export const paginationFields = () =>
|
|
9
|
+
z.object({
|
|
10
|
+
cursor: z.string().optional(),
|
|
11
|
+
limit: z.number().optional().default(20),
|
|
12
|
+
offset: z.number().optional().default(0),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/** Paginated output wrapper for a given item schema. */
|
|
16
|
+
export const paginatedOutput = <T>(itemSchema: z.ZodType<T>) =>
|
|
17
|
+
z.object({
|
|
18
|
+
hasMore: z.boolean(),
|
|
19
|
+
items: z.array(itemSchema),
|
|
20
|
+
nextCursor: z.string().optional(),
|
|
21
|
+
total: z.number(),
|
|
22
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress schema helpers for @ontrails/core/patterns
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
/** Progress tracking fields. */
|
|
8
|
+
export const progressFields = () =>
|
|
9
|
+
z.object({
|
|
10
|
+
current: z.number(),
|
|
11
|
+
percentage: z.number().optional(),
|
|
12
|
+
total: z.number(),
|
|
13
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sorting schema helpers for @ontrails/core/patterns
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
/** Sort fields constrained to a set of allowed column names. */
|
|
8
|
+
export const sortFields = <const T extends string>(
|
|
9
|
+
allowedFields: [T, ...T[]]
|
|
10
|
+
) =>
|
|
11
|
+
z.object({
|
|
12
|
+
sortBy: z.enum(allowedFields).optional(),
|
|
13
|
+
sortOrder: z.enum(['asc', 'desc']).optional().default('asc'),
|
|
14
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status schema helpers for @ontrails/core/patterns
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
/** Standard workflow status field. */
|
|
8
|
+
export const statusFields = () =>
|
|
9
|
+
z.object({
|
|
10
|
+
status: z.enum(['pending', 'running', 'completed', 'failed', 'cancelled']),
|
|
11
|
+
});
|
package/src/permits.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/** Minimal permit shape available on TrailContext. */
|
|
4
|
+
export interface BasePermit {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly scopes: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const basePermitSchema: z.ZodType<BasePermit> = z.object({
|
|
10
|
+
id: z.string(),
|
|
11
|
+
scopes: z.array(z.string()).readonly(),
|
|
12
|
+
});
|
package/src/queue.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActivationSource,
|
|
3
|
+
ActivationSourceMeta,
|
|
4
|
+
ActivationSourceParse,
|
|
5
|
+
} from './activation-source.js';
|
|
6
|
+
import { ValidationError } from './errors.js';
|
|
7
|
+
|
|
8
|
+
export interface QueueSpec<TOutput = unknown> {
|
|
9
|
+
readonly meta?: ActivationSourceMeta | undefined;
|
|
10
|
+
readonly parse: ActivationSourceParse<TOutput>;
|
|
11
|
+
readonly payload?: ActivationSource['payload'] | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Runtime queue name. Defaults to the source id, so authored queue
|
|
14
|
+
* contracts can stay stable while host bindings choose their own names.
|
|
15
|
+
*/
|
|
16
|
+
readonly queue?: string | undefined;
|
|
17
|
+
/** Reserved for future queue-specific design; trail versioning is trail-only. */
|
|
18
|
+
readonly version?: never;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface QueueSource<TOutput = unknown> extends ActivationSource {
|
|
22
|
+
readonly kind: 'queue';
|
|
23
|
+
readonly meta?: ActivationSourceMeta | undefined;
|
|
24
|
+
readonly parse: ActivationSourceParse<TOutput>;
|
|
25
|
+
readonly payload?: ActivationSource['payload'] | undefined;
|
|
26
|
+
readonly queue: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface QueueValidationIssue {
|
|
30
|
+
readonly field: 'parse' | 'queue';
|
|
31
|
+
readonly message: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const normalizeQueueName = (queueName: string): string => queueName.trim();
|
|
35
|
+
|
|
36
|
+
const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
|
|
37
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
38
|
+
|
|
39
|
+
const isZodSchema = (value: unknown): boolean =>
|
|
40
|
+
isObjectRecord(value) && typeof value['safeParse'] === 'function';
|
|
41
|
+
|
|
42
|
+
const validateQueueName = (queueName: unknown): QueueValidationIssue[] =>
|
|
43
|
+
typeof queueName === 'string' && queueName.trim().length > 0
|
|
44
|
+
? []
|
|
45
|
+
: [
|
|
46
|
+
{
|
|
47
|
+
field: 'queue',
|
|
48
|
+
message: 'Queue source must define a non-empty queue name',
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const validateRequiredParse = (parse: unknown): QueueValidationIssue[] =>
|
|
53
|
+
parse === undefined
|
|
54
|
+
? [
|
|
55
|
+
{
|
|
56
|
+
field: 'parse',
|
|
57
|
+
message: 'Queue sources must define parse',
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
: [];
|
|
61
|
+
|
|
62
|
+
const validateParseShape = (parse: unknown): QueueValidationIssue[] => {
|
|
63
|
+
if (parse === undefined || isZodSchema(parse)) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
if (isObjectRecord(parse) && isZodSchema(parse['output'])) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
field: 'parse',
|
|
72
|
+
message: 'Queue parse must be a Zod schema or define parse.output',
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const queueIssuesMessage = (
|
|
78
|
+
id: string,
|
|
79
|
+
issues: readonly QueueValidationIssue[]
|
|
80
|
+
): string =>
|
|
81
|
+
`queue("${id}") is invalid: ${issues.map((issue) => `${issue.field}: ${issue.message}`).join('; ')}`;
|
|
82
|
+
|
|
83
|
+
const assertQueueSpec = <TOutput>(
|
|
84
|
+
id: string,
|
|
85
|
+
spec: QueueSpec<TOutput>
|
|
86
|
+
): {
|
|
87
|
+
readonly queue: string;
|
|
88
|
+
} => {
|
|
89
|
+
const queueName = spec.queue === undefined ? id : spec.queue;
|
|
90
|
+
const issues = [
|
|
91
|
+
...validateQueueName(queueName),
|
|
92
|
+
...validateRequiredParse(spec.parse),
|
|
93
|
+
...validateParseShape(spec.parse),
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
if (issues.length > 0) {
|
|
97
|
+
throw new ValidationError(queueIssuesMessage(id, issues), {
|
|
98
|
+
context: { issues },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { queue: normalizeQueueName(queueName) };
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const validateQueueSource = (
|
|
106
|
+
source: ActivationSource
|
|
107
|
+
): readonly QueueValidationIssue[] => {
|
|
108
|
+
if (source.kind !== 'queue') {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
...validateQueueName(source.queue),
|
|
114
|
+
...validateRequiredParse(source.parse),
|
|
115
|
+
...validateParseShape(source.parse),
|
|
116
|
+
];
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Define a queue activation source.
|
|
121
|
+
*
|
|
122
|
+
* Queue sources are inert contract data: they describe which runtime queue
|
|
123
|
+
* wakes a trail and how the message body becomes trail input. A host adapter
|
|
124
|
+
* such as `@ontrails/cloudflare/workers` owns delivery.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* import { queue } from '@ontrails/core';
|
|
129
|
+
* import { z } from 'zod';
|
|
130
|
+
*
|
|
131
|
+
* const source = queue('queue.email.outbox', {
|
|
132
|
+
* queue: 'email-outbox',
|
|
133
|
+
* parse: z.object({ messageId: z.string() }),
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export function queue<TOutput>(
|
|
138
|
+
id: string,
|
|
139
|
+
spec: QueueSpec<TOutput>
|
|
140
|
+
): QueueSource<TOutput>;
|
|
141
|
+
export function queue<TOutput>(
|
|
142
|
+
spec: QueueSpec<TOutput> & { readonly id: string }
|
|
143
|
+
): QueueSource<TOutput>;
|
|
144
|
+
export function queue<TOutput>(
|
|
145
|
+
idOrSpec: string | (QueueSpec<TOutput> & { readonly id: string }),
|
|
146
|
+
maybeSpec?: QueueSpec<TOutput>
|
|
147
|
+
): QueueSource<TOutput> {
|
|
148
|
+
const id = typeof idOrSpec === 'string' ? idOrSpec : idOrSpec.id;
|
|
149
|
+
// oxlint-disable-next-line no-non-null-assertion -- overload guarantees maybeSpec when idOrSpec is string
|
|
150
|
+
const spec = typeof idOrSpec === 'string' ? maybeSpec! : idOrSpec;
|
|
151
|
+
const normalized = assertQueueSpec(id, spec);
|
|
152
|
+
|
|
153
|
+
return Object.freeze({
|
|
154
|
+
id,
|
|
155
|
+
kind: 'queue' as const,
|
|
156
|
+
parse: spec.parse,
|
|
157
|
+
queue: normalized.queue,
|
|
158
|
+
...(spec.meta === undefined
|
|
159
|
+
? {}
|
|
160
|
+
: { meta: Object.freeze({ ...spec.meta }) }),
|
|
161
|
+
...(spec.payload === undefined ? {} : { payload: spec.payload }),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default redaction patterns and sensitive key lists.
|
|
3
|
+
*
|
|
4
|
+
* These are used by {@link createRedactor} to strip secrets from strings
|
|
5
|
+
* and object values before they reach logs, traces, or error payloads.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Regex patterns that match sensitive values inside arbitrary strings
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_PATTERNS: RegExp[] = [
|
|
13
|
+
// Credit card numbers: 4 groups of 4 digits separated by spaces or dashes
|
|
14
|
+
/\b\d{4}[- ]\d{4}[- ]\d{4}[- ]\d{4}\b/g,
|
|
15
|
+
|
|
16
|
+
// SSN: XXX-XX-XXXX
|
|
17
|
+
/\b\d{3}-\d{2}-\d{4}\b/g,
|
|
18
|
+
|
|
19
|
+
// Bearer tokens. Keep the threshold above short prose like "Bearer token".
|
|
20
|
+
/Bearer\s+[A-Za-z0-9\-._~+/=]{6,}/g,
|
|
21
|
+
|
|
22
|
+
// Basic auth
|
|
23
|
+
/Basic\s+[A-Za-z0-9+/=]{8,}/g,
|
|
24
|
+
|
|
25
|
+
// Common key/value secrets embedded in strings
|
|
26
|
+
/\b(?:password|secret|token|api[_-]?key|cookie)\s*[:=]\s*[^\s,;]+/gi,
|
|
27
|
+
|
|
28
|
+
// API keys: sk-*, pk_*, sk_* prefixed tokens
|
|
29
|
+
/\b(?:sk-|pk_|sk_)[A-Za-z0-9_-]{8,}\b/g,
|
|
30
|
+
|
|
31
|
+
// JWT tokens: eyJ... (three base64url segments separated by dots)
|
|
32
|
+
/\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Object keys whose values should always be redacted
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
export const DEFAULT_SENSITIVE_KEYS: string[] = [
|
|
40
|
+
'password',
|
|
41
|
+
'secret',
|
|
42
|
+
'token',
|
|
43
|
+
'apiKey',
|
|
44
|
+
'api_key',
|
|
45
|
+
'authorization',
|
|
46
|
+
'cookie',
|
|
47
|
+
'ssn',
|
|
48
|
+
'creditCard',
|
|
49
|
+
'credit_card',
|
|
50
|
+
];
|