@beignet/core 0.0.31 → 0.0.33
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 +115 -0
- package/README.md +346 -21
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +63 -47
- package/dist/application/index.js.map +1 -1
- package/dist/domain/entity.d.ts +37 -6
- package/dist/domain/entity.d.ts.map +1 -1
- package/dist/domain/entity.js +9 -7
- package/dist/domain/entity.js.map +1 -1
- package/dist/domain/index.d.ts +4 -4
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +4 -4
- package/dist/domain/index.js.map +1 -1
- package/dist/events/index.d.ts +6 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +20 -10
- package/dist/events/index.js.map +1 -1
- package/dist/flags/index.d.ts +5 -4
- package/dist/flags/index.d.ts.map +1 -1
- package/dist/flags/index.js +10 -9
- package/dist/flags/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +11 -1
- package/dist/idempotency/index.d.ts.map +1 -1
- package/dist/idempotency/index.js +4 -3
- package/dist/idempotency/index.js.map +1 -1
- package/dist/jobs/index.d.ts +449 -9
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +472 -12
- package/dist/jobs/index.js.map +1 -1
- package/dist/outbox/index.d.ts +170 -3
- package/dist/outbox/index.d.ts.map +1 -1
- package/dist/outbox/index.js +192 -8
- package/dist/outbox/index.js.map +1 -1
- package/dist/ports/events.d.ts +2 -2
- package/dist/ports/index.d.ts +6 -1
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +1 -0
- package/dist/ports/index.js.map +1 -1
- package/dist/providers/instrumentation.d.ts +4 -0
- package/dist/providers/instrumentation.d.ts.map +1 -1
- package/dist/providers/instrumentation.js.map +1 -1
- package/dist/providers/provider.d.ts +10 -2
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/providers/provider.js.map +1 -1
- package/dist/schedules/index.d.ts +8 -7
- package/dist/schedules/index.d.ts.map +1 -1
- package/dist/schedules/index.js +47 -16
- package/dist/schedules/index.js.map +1 -1
- package/dist/search/index.d.ts +2 -1
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +1 -0
- package/dist/search/index.js.map +1 -1
- package/dist/server/hooks/index.d.ts +1 -0
- package/dist/server/hooks/index.d.ts.map +1 -1
- package/dist/server/hooks/index.js +1 -0
- package/dist/server/hooks/index.js.map +1 -1
- package/dist/server/hooks/rate-limit.d.ts +26 -13
- package/dist/server/hooks/rate-limit.d.ts.map +1 -1
- package/dist/server/hooks/rate-limit.js +28 -34
- package/dist/server/hooks/rate-limit.js.map +1 -1
- package/dist/server/hooks/security.d.ts +179 -0
- package/dist/server/hooks/security.d.ts.map +1 -0
- package/dist/server/hooks/security.js +225 -0
- package/dist/server/hooks/security.js.map +1 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/instrumentation.d.ts.map +1 -1
- package/dist/server/instrumentation.js +22 -6
- package/dist/server/instrumentation.js.map +1 -1
- package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
- package/dist/server/providers/loadProviderConfig.js +15 -2
- package/dist/server/providers/loadProviderConfig.js.map +1 -1
- package/dist/server/request-context.d.ts +4 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +3 -0
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/runtime-integrity.d.ts +84 -0
- package/dist/server/runtime-integrity.d.ts.map +1 -0
- package/dist/server/runtime-integrity.js +160 -0
- package/dist/server/runtime-integrity.js.map +1 -0
- package/dist/server/server.d.ts +10 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +55 -1
- package/dist/server/server.js.map +1 -1
- package/dist/server/trusted-proxy.d.ts +77 -0
- package/dist/server/trusted-proxy.d.ts.map +1 -0
- package/dist/server/trusted-proxy.js +129 -0
- package/dist/server/trusted-proxy.js.map +1 -0
- package/dist/tasks/index.d.ts +6 -7
- package/dist/tasks/index.d.ts.map +1 -1
- package/dist/tasks/index.js +22 -5
- package/dist/tasks/index.js.map +1 -1
- package/dist/tenancy/index.d.ts +41 -0
- package/dist/tenancy/index.d.ts.map +1 -0
- package/dist/tenancy/index.js +34 -0
- package/dist/tenancy/index.js.map +1 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +12 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/tracing/execution.d.ts +14 -0
- package/dist/tracing/execution.d.ts.map +1 -0
- package/dist/tracing/execution.js +17 -0
- package/dist/tracing/execution.js.map +1 -0
- package/dist/tracing/index.d.ts +49 -0
- package/dist/tracing/index.d.ts.map +1 -1
- package/dist/tracing/index.js +59 -0
- package/dist/tracing/index.js.map +1 -1
- package/dist/uploads/client.d.ts.map +1 -1
- package/dist/uploads/client.js +44 -6
- package/dist/uploads/client.js.map +1 -1
- package/dist/uploads/index.d.ts +182 -2
- package/dist/uploads/index.d.ts.map +1 -1
- package/dist/uploads/index.js +468 -76
- package/dist/uploads/index.js.map +1 -1
- package/dist/webhooks/index.d.ts +56 -4
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/index.js +107 -2
- package/dist/webhooks/index.js.map +1 -1
- package/package.json +5 -1
- package/skills/app-architecture/SKILL.md +23 -2
- package/src/application/index.ts +88 -56
- package/src/domain/entity.ts +61 -13
- package/src/domain/index.ts +8 -7
- package/src/events/index.ts +27 -14
- package/src/flags/index.ts +23 -19
- package/src/idempotency/index.ts +17 -3
- package/src/jobs/index.ts +1022 -19
- package/src/outbox/index.ts +409 -9
- package/src/ports/events.ts +2 -2
- package/src/ports/index.ts +17 -0
- package/src/providers/instrumentation.ts +4 -0
- package/src/providers/provider.ts +11 -2
- package/src/schedules/index.ts +60 -26
- package/src/search/index.ts +3 -1
- package/src/server/hooks/index.ts +10 -0
- package/src/server/hooks/rate-limit.ts +52 -46
- package/src/server/hooks/security.ts +503 -0
- package/src/server/index.ts +8 -0
- package/src/server/instrumentation.ts +25 -5
- package/src/server/providers/loadProviderConfig.ts +19 -2
- package/src/server/request-context.ts +7 -0
- package/src/server/runtime-integrity.ts +322 -0
- package/src/server/server.ts +86 -1
- package/src/server/trusted-proxy.ts +249 -0
- package/src/tasks/index.ts +29 -12
- package/src/tenancy/index.ts +55 -0
- package/src/testing/index.ts +19 -3
- package/src/tracing/execution.ts +37 -0
- package/src/tracing/index.ts +137 -0
- package/src/uploads/client.ts +65 -6
- package/src/uploads/index.ts +713 -76
- package/src/webhooks/index.ts +240 -9
- package/src/domain/events.ts +0 -59
package/src/tasks/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { runWithResolvedTracingContext } from "../tracing/execution.js";
|
|
3
|
+
import type { TracingPort } from "../tracing/index.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Any Standard Schema compatible validator.
|
|
@@ -75,9 +77,7 @@ export interface TaskHandleArgs<T extends TaskDef, Ctx> {
|
|
|
75
77
|
* Parsed task input.
|
|
76
78
|
*/
|
|
77
79
|
input: InferTaskInput<T>;
|
|
78
|
-
/**
|
|
79
|
-
* Handler context.
|
|
80
|
-
*/
|
|
80
|
+
/** Handler context. */
|
|
81
81
|
ctx: Ctx;
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -115,10 +115,10 @@ export interface RunTaskOptions<Ctx> {
|
|
|
115
115
|
* handler runs.
|
|
116
116
|
*/
|
|
117
117
|
input: unknown;
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
/** Handler context or factory resolved inside the task span. */
|
|
119
|
+
ctx: Ctx | (() => MaybePromise<Ctx>);
|
|
120
|
+
/** Runtime tracing port used before a lazy context factory runs. */
|
|
121
|
+
tracing?: TracingPort;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/**
|
|
@@ -260,12 +260,29 @@ export async function runTask<
|
|
|
260
260
|
T extends TaskDef<string, StandardSchema, Ctx>,
|
|
261
261
|
Ctx,
|
|
262
262
|
>(task: T, options: RunTaskOptions<Ctx>): Promise<InferTaskOutput<T>> {
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
263
|
+
const traceAttributes = {
|
|
264
|
+
"beignet.task.name": task.name,
|
|
265
|
+
} as const;
|
|
266
|
+
|
|
267
|
+
return await runWithResolvedTracingContext({
|
|
268
|
+
tracing: options.tracing,
|
|
267
269
|
ctx: options.ctx,
|
|
268
|
-
|
|
270
|
+
operation: {
|
|
271
|
+
name: `beignet.task ${task.name}`,
|
|
272
|
+
type: "task",
|
|
273
|
+
kind: "internal",
|
|
274
|
+
attributes: traceAttributes,
|
|
275
|
+
metricAttributes: traceAttributes,
|
|
276
|
+
},
|
|
277
|
+
run: async (ctx) => {
|
|
278
|
+
const parsed = await parseTaskInput(task, options.input);
|
|
279
|
+
return (await task.handle({
|
|
280
|
+
task,
|
|
281
|
+
input: parsed,
|
|
282
|
+
ctx,
|
|
283
|
+
})) as InferTaskOutput<T>;
|
|
284
|
+
},
|
|
285
|
+
});
|
|
269
286
|
}
|
|
270
287
|
|
|
271
288
|
/**
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ActivityTenant } from "../ports/audit.js";
|
|
2
|
+
import { type RequireOptions, requireTenant } from "../ports/auth.js";
|
|
3
|
+
|
|
4
|
+
const tenantScopeBrand = Symbol("TenantScope");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Branded tenant scope token for repository boundaries.
|
|
8
|
+
*
|
|
9
|
+
* Apps still own tenant resolution and tenant data modeling. `TenantScope`
|
|
10
|
+
* carries the already-resolved activity tenant through app-facing repository
|
|
11
|
+
* ports so adapters can apply tenant predicates without accepting arbitrary
|
|
12
|
+
* caller-provided tenant IDs.
|
|
13
|
+
*/
|
|
14
|
+
export type TenantScope = Readonly<{
|
|
15
|
+
id: string;
|
|
16
|
+
tenant: ActivityTenant;
|
|
17
|
+
readonly [tenantScopeBrand]: true;
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a tenant scope from a resolved activity tenant.
|
|
22
|
+
*
|
|
23
|
+
* This does not load, create, or validate a tenant record. It only brands the
|
|
24
|
+
* already-resolved tenant for repository and adapter boundaries.
|
|
25
|
+
*/
|
|
26
|
+
export function createTenantScope(tenant: ActivityTenant): TenantScope {
|
|
27
|
+
return {
|
|
28
|
+
id: tenant.id,
|
|
29
|
+
tenant,
|
|
30
|
+
[tenantScopeBrand]: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Return a branded tenant scope from `ctx.tenant` or throw.
|
|
36
|
+
*
|
|
37
|
+
* Throws `TenantRequiredError` by default through `requireTenant`. Pass
|
|
38
|
+
* `options.error` to throw an app-owned error instead.
|
|
39
|
+
*/
|
|
40
|
+
export function requireTenantScope(
|
|
41
|
+
ctx: { tenant?: ActivityTenant | null },
|
|
42
|
+
options?: RequireOptions,
|
|
43
|
+
): TenantScope {
|
|
44
|
+
return createTenantScope(requireTenant(ctx, options));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Extract the stable tenant ID from a tenant scope.
|
|
49
|
+
*
|
|
50
|
+
* Repository adapters should use this helper at the persistence boundary
|
|
51
|
+
* instead of accepting raw tenant IDs from use cases.
|
|
52
|
+
*/
|
|
53
|
+
export function tenantScopeId(scope: TenantScope): string {
|
|
54
|
+
return scope.id;
|
|
55
|
+
}
|
package/src/testing/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ import type {
|
|
|
66
66
|
} from "../providers/provider.js";
|
|
67
67
|
import { createMemorySearch, type MemorySearchPort } from "../search/index.js";
|
|
68
68
|
import { createAmbientAuditLog } from "../server/audit-context.js";
|
|
69
|
+
import { loadProviderConfig } from "../server/providers/loadProviderConfig.js";
|
|
69
70
|
import {
|
|
70
71
|
clearActiveRequestContext,
|
|
71
72
|
enterActiveRequestContext,
|
|
@@ -377,8 +378,10 @@ export function createTestPorts<
|
|
|
377
378
|
const errorReporter = createMemoryErrorReporter();
|
|
378
379
|
const flags = createMemoryFlags();
|
|
379
380
|
const { jobs, dispatchedJobs } = createRecordingJobDispatcher();
|
|
380
|
-
const idempotency = createMemoryIdempotencyStore();
|
|
381
381
|
const ids = options.ids ?? createUuidIdGenerator();
|
|
382
|
+
const idempotency = createMemoryIdempotencyStore({
|
|
383
|
+
now: () => clock.now(),
|
|
384
|
+
});
|
|
382
385
|
const logger = createNoopLogger();
|
|
383
386
|
const locks = createMemoryLocks({
|
|
384
387
|
now: () => clock.now(),
|
|
@@ -386,7 +389,10 @@ export function createTestPorts<
|
|
|
386
389
|
});
|
|
387
390
|
const mailer = createMemoryMailer();
|
|
388
391
|
const notifications = createMemoryNotificationPort();
|
|
389
|
-
const outbox = createMemoryOutbox(
|
|
392
|
+
const outbox = createMemoryOutbox({
|
|
393
|
+
id: () => ids.nextId(),
|
|
394
|
+
now: () => clock.now(),
|
|
395
|
+
});
|
|
390
396
|
const payments = createMemoryPayments();
|
|
391
397
|
const rateLimit = createMemoryRateLimiter();
|
|
392
398
|
const search = createMemorySearch();
|
|
@@ -983,6 +989,12 @@ function createUnboundPortGuard<Ports extends AnyPorts>(ports: Ports): Ports {
|
|
|
983
989
|
* Options for `installProviderForTest(...)`.
|
|
984
990
|
*/
|
|
985
991
|
export interface InstallProviderForTestOptions {
|
|
992
|
+
/**
|
|
993
|
+
* Environment variables used to resolve the provider's config through the
|
|
994
|
+
* same loader the server uses (including the provider's `overrides`).
|
|
995
|
+
* Ignored when `config` is passed explicitly.
|
|
996
|
+
*/
|
|
997
|
+
env?: Record<string, string | undefined>;
|
|
986
998
|
/**
|
|
987
999
|
* Base app ports visible to provider setup, such as `{ devtools }`.
|
|
988
1000
|
*/
|
|
@@ -1047,9 +1059,13 @@ export async function installProviderForTest(
|
|
|
1047
1059
|
"Pass createServiceContext to installProviderForTest(...) when the test needs a service context.",
|
|
1048
1060
|
);
|
|
1049
1061
|
});
|
|
1062
|
+
const config =
|
|
1063
|
+
options.config !== undefined
|
|
1064
|
+
? options.config
|
|
1065
|
+
: await loadProviderConfig(provider, options.env ?? {}, {});
|
|
1050
1066
|
const result = await provider.setup({
|
|
1051
1067
|
ports: basePorts,
|
|
1052
|
-
config
|
|
1068
|
+
config,
|
|
1053
1069
|
createServiceContext,
|
|
1054
1070
|
});
|
|
1055
1071
|
const ports: Record<string, unknown> = { ...basePorts, ...result.ports };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveTracingPort,
|
|
3
|
+
runWithTracing,
|
|
4
|
+
type TraceOperation,
|
|
5
|
+
type TraceSpan,
|
|
6
|
+
type TracingPort,
|
|
7
|
+
} from "./index.js";
|
|
8
|
+
|
|
9
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a lazy application context inside a span when the runtime tracing
|
|
13
|
+
* port is available independently from that context.
|
|
14
|
+
*/
|
|
15
|
+
export async function runWithResolvedTracingContext<Ctx, Result>(args: {
|
|
16
|
+
tracing?: TracingPort;
|
|
17
|
+
ctx: Ctx | (() => MaybePromise<Ctx>);
|
|
18
|
+
operation: TraceOperation;
|
|
19
|
+
run(ctx: Ctx, span?: TraceSpan): MaybePromise<Result>;
|
|
20
|
+
}): Promise<Result> {
|
|
21
|
+
const resolveContext = async (): Promise<Ctx> =>
|
|
22
|
+
typeof args.ctx === "function"
|
|
23
|
+
? await (args.ctx as () => MaybePromise<Ctx>)()
|
|
24
|
+
: args.ctx;
|
|
25
|
+
|
|
26
|
+
if (args.tracing) {
|
|
27
|
+
return await runWithTracing(args.tracing, args.operation, async (span) =>
|
|
28
|
+
args.run(await resolveContext(), span),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const ctx = await resolveContext();
|
|
33
|
+
const tracing = resolveTracingPort(ctx);
|
|
34
|
+
return await runWithTracing(tracing, args.operation, (span) =>
|
|
35
|
+
args.run(ctx, span),
|
|
36
|
+
);
|
|
37
|
+
}
|
package/src/tracing/index.ts
CHANGED
|
@@ -30,6 +30,10 @@ export interface TraceContext {
|
|
|
30
30
|
* W3C traceparent header value.
|
|
31
31
|
*/
|
|
32
32
|
traceparent: string;
|
|
33
|
+
/**
|
|
34
|
+
* Optional W3C tracestate value associated with this context.
|
|
35
|
+
*/
|
|
36
|
+
tracestate?: string;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
/**
|
|
@@ -62,6 +66,137 @@ export interface TraceContextInput {
|
|
|
62
66
|
spanId?: string;
|
|
63
67
|
parentSpanId?: string;
|
|
64
68
|
traceparent?: string;
|
|
69
|
+
tracestate?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Values accepted as tracing attributes. */
|
|
73
|
+
export type TraceAttributeValue =
|
|
74
|
+
| string
|
|
75
|
+
| number
|
|
76
|
+
| boolean
|
|
77
|
+
| readonly string[]
|
|
78
|
+
| readonly number[]
|
|
79
|
+
| readonly boolean[];
|
|
80
|
+
|
|
81
|
+
/** Attributes attached to a traced Beignet operation. */
|
|
82
|
+
export type TraceAttributes = Readonly<Record<string, TraceAttributeValue>>;
|
|
83
|
+
|
|
84
|
+
/** Span kinds understood by tracing providers. */
|
|
85
|
+
export type TraceSpanKind =
|
|
86
|
+
| "internal"
|
|
87
|
+
| "server"
|
|
88
|
+
| "client"
|
|
89
|
+
| "producer"
|
|
90
|
+
| "consumer";
|
|
91
|
+
|
|
92
|
+
/** Stable Beignet operation categories used by tracing and metrics providers. */
|
|
93
|
+
export type TraceOperationType =
|
|
94
|
+
| "request"
|
|
95
|
+
| "useCase"
|
|
96
|
+
| "listener"
|
|
97
|
+
| "job"
|
|
98
|
+
| "schedule"
|
|
99
|
+
| "task"
|
|
100
|
+
| "outbox"
|
|
101
|
+
| "provider";
|
|
102
|
+
|
|
103
|
+
/** Description of one operation to run inside an active trace span. */
|
|
104
|
+
export interface TraceOperation {
|
|
105
|
+
name: string;
|
|
106
|
+
type: TraceOperationType;
|
|
107
|
+
kind?: TraceSpanKind;
|
|
108
|
+
parent?: TraceContextInput;
|
|
109
|
+
/** Attributes attached to the operation span. */
|
|
110
|
+
attributes?: TraceAttributes;
|
|
111
|
+
/**
|
|
112
|
+
* Bounded attributes attached to operation metrics. Values must describe a
|
|
113
|
+
* stable operation dimension, never a request, actor, tenant, or payload.
|
|
114
|
+
*/
|
|
115
|
+
metricAttributes?: TraceAttributes;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Framework-neutral span handle exposed to Beignet execution wrappers. */
|
|
119
|
+
export interface TraceSpan {
|
|
120
|
+
readonly context: TraceContext;
|
|
121
|
+
setAttribute(name: string, value: TraceAttributeValue): void;
|
|
122
|
+
setAttributes(attributes: TraceAttributes): void;
|
|
123
|
+
addEvent(name: string, attributes?: TraceAttributes): void;
|
|
124
|
+
setStatus(status: "ok" | "error"): void;
|
|
125
|
+
recordError(error: unknown): void;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Optional app port implemented by production tracing integrations. */
|
|
129
|
+
export interface TracingPort {
|
|
130
|
+
current(): TraceContext | undefined;
|
|
131
|
+
startActiveSpan<T>(operation: TraceOperation, run: (span: TraceSpan) => T): T;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
135
|
+
return typeof value === "object" && value !== null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Return whether a value implements `TracingPort`. */
|
|
139
|
+
export function isTracingPort(value: unknown): value is TracingPort {
|
|
140
|
+
return (
|
|
141
|
+
isObject(value) &&
|
|
142
|
+
"current" in value &&
|
|
143
|
+
"startActiveSpan" in value &&
|
|
144
|
+
typeof value.current === "function" &&
|
|
145
|
+
typeof value.startActiveSpan === "function"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Resolve a tracing port from a direct port, ports object, or app context. */
|
|
150
|
+
export function resolveTracingPort(target: unknown): TracingPort | undefined {
|
|
151
|
+
if (!target) return undefined;
|
|
152
|
+
if (isTracingPort(target)) return target;
|
|
153
|
+
if (!isObject(target)) return undefined;
|
|
154
|
+
|
|
155
|
+
const direct = "tracing" in target ? (target.tracing as unknown) : undefined;
|
|
156
|
+
if (isTracingPort(direct)) return direct;
|
|
157
|
+
|
|
158
|
+
const ports = "ports" in target ? target.ports : undefined;
|
|
159
|
+
if (!ports || ports === target) return undefined;
|
|
160
|
+
return resolveTracingPort(ports);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Resolve trace fields from a trace context or context-like object. */
|
|
164
|
+
export function resolveTraceContextInput(
|
|
165
|
+
target: unknown,
|
|
166
|
+
): TraceContextInput | undefined {
|
|
167
|
+
if (!isObject(target)) return undefined;
|
|
168
|
+
|
|
169
|
+
const context = target as TraceContextInput;
|
|
170
|
+
if (
|
|
171
|
+
context.traceId ||
|
|
172
|
+
context.spanId ||
|
|
173
|
+
context.parentSpanId ||
|
|
174
|
+
context.traceparent ||
|
|
175
|
+
context.tracestate
|
|
176
|
+
) {
|
|
177
|
+
return {
|
|
178
|
+
...(context.traceId ? { traceId: context.traceId } : {}),
|
|
179
|
+
...(context.spanId ? { spanId: context.spanId } : {}),
|
|
180
|
+
...(context.parentSpanId ? { parentSpanId: context.parentSpanId } : {}),
|
|
181
|
+
...(context.traceparent ? { traceparent: context.traceparent } : {}),
|
|
182
|
+
...(context.tracestate ? { tracestate: context.tracestate } : {}),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const traceContext = "trace" in target ? target.trace : undefined;
|
|
187
|
+
if (!traceContext || traceContext === target) return undefined;
|
|
188
|
+
return resolveTraceContextInput(traceContext);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Run a callback inside a tracing span when an app tracing port is installed. */
|
|
192
|
+
export function runWithTracing<T>(
|
|
193
|
+
target: unknown,
|
|
194
|
+
operation: TraceOperation,
|
|
195
|
+
run: (span?: TraceSpan) => T,
|
|
196
|
+
): T {
|
|
197
|
+
const tracing = resolveTracingPort(target);
|
|
198
|
+
if (!tracing) return run();
|
|
199
|
+
return tracing.startActiveSpan(operation, (span) => run(span));
|
|
65
200
|
}
|
|
66
201
|
|
|
67
202
|
function createHexId(length: number): string {
|
|
@@ -159,6 +294,7 @@ export function createTraceContext(
|
|
|
159
294
|
spanId,
|
|
160
295
|
traceFlags: parsed?.traceFlags,
|
|
161
296
|
}),
|
|
297
|
+
...(input.tracestate ? { tracestate: input.tracestate } : {}),
|
|
162
298
|
};
|
|
163
299
|
}
|
|
164
300
|
|
|
@@ -172,5 +308,6 @@ export function createChildTraceContext(
|
|
|
172
308
|
traceId: parent.traceId,
|
|
173
309
|
parentSpanId: parent.spanId,
|
|
174
310
|
traceparent: parent.traceparent,
|
|
311
|
+
tracestate: parent.tracestate,
|
|
175
312
|
});
|
|
176
313
|
}
|
package/src/uploads/client.ts
CHANGED
|
@@ -391,6 +391,17 @@ export function createUploadClient<
|
|
|
391
391
|
uploadName: Name,
|
|
392
392
|
prepareOptions: UploadClientPrepareOptions<UploadByName<Registry, Name>>,
|
|
393
393
|
) {
|
|
394
|
+
const fileConstraints = constraints(uploadName);
|
|
395
|
+
const files = await Promise.all(
|
|
396
|
+
prepareOptions.files.map((file) =>
|
|
397
|
+
fileIntentFromFile({
|
|
398
|
+
file,
|
|
399
|
+
constraints: fileConstraints,
|
|
400
|
+
uploadName,
|
|
401
|
+
}),
|
|
402
|
+
),
|
|
403
|
+
);
|
|
404
|
+
|
|
394
405
|
return requestJson<PrepareUploadResult>({
|
|
395
406
|
fetchImpl,
|
|
396
407
|
url: actionUrl(baseUrl, uploadName, "prepare"),
|
|
@@ -403,7 +414,7 @@ export function createUploadClient<
|
|
|
403
414
|
signal: prepareOptions.signal,
|
|
404
415
|
body: JSON.stringify({
|
|
405
416
|
metadata: prepareOptions.metadata,
|
|
406
|
-
files
|
|
417
|
+
files,
|
|
407
418
|
}),
|
|
408
419
|
},
|
|
409
420
|
});
|
|
@@ -564,12 +575,28 @@ export function createUploadClient<
|
|
|
564
575
|
};
|
|
565
576
|
}
|
|
566
577
|
|
|
567
|
-
function fileIntentFromFile(
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
578
|
+
async function fileIntentFromFile(args: {
|
|
579
|
+
file: File;
|
|
580
|
+
constraints: UploadFileConstraints | undefined;
|
|
581
|
+
uploadName: string;
|
|
582
|
+
}): Promise<UploadFileIntent> {
|
|
583
|
+
const contentType =
|
|
584
|
+
normalizeContentType(args.file.type) || "application/octet-stream";
|
|
585
|
+
const intent: UploadFileIntent = {
|
|
586
|
+
name: args.file.name,
|
|
587
|
+
contentType,
|
|
588
|
+
size: args.file.size,
|
|
572
589
|
};
|
|
590
|
+
|
|
591
|
+
const checksumRequirement = args.constraints?.checksum;
|
|
592
|
+
if (checksumRequirement?.algorithm === "sha256") {
|
|
593
|
+
intent.checksum = await createClientUploadChecksum(args.file, {
|
|
594
|
+
uploadName: args.uploadName,
|
|
595
|
+
required: checksumRequirement.required !== false,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return intent;
|
|
573
600
|
}
|
|
574
601
|
|
|
575
602
|
function completeFileInput(file: PreparedUploadResultFile): PreparedUploadFile {
|
|
@@ -577,15 +604,47 @@ function completeFileInput(file: PreparedUploadResultFile): PreparedUploadFile {
|
|
|
577
604
|
name: file.name,
|
|
578
605
|
contentType: file.contentType,
|
|
579
606
|
size: file.size,
|
|
607
|
+
...(file.checksum ? { checksum: file.checksum } : {}),
|
|
580
608
|
uploadId: file.uploadId,
|
|
581
609
|
key: file.key,
|
|
582
610
|
};
|
|
583
611
|
}
|
|
584
612
|
|
|
613
|
+
async function createClientUploadChecksum(
|
|
614
|
+
file: File,
|
|
615
|
+
options: { uploadName: string; required: boolean },
|
|
616
|
+
): Promise<UploadFileIntent["checksum"]> {
|
|
617
|
+
if (!globalThis.crypto?.subtle) {
|
|
618
|
+
if (!options.required) return undefined;
|
|
619
|
+
|
|
620
|
+
throw new UploadClientError({
|
|
621
|
+
operation: "prepare upload",
|
|
622
|
+
uploadName: options.uploadName,
|
|
623
|
+
code: "UPLOAD_CHECKSUM_UNAVAILABLE",
|
|
624
|
+
message: `Upload "${options.uploadName}" requires Web Crypto to compute checksums.`,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const digest = await globalThis.crypto.subtle.digest(
|
|
629
|
+
"SHA-256",
|
|
630
|
+
await file.arrayBuffer(),
|
|
631
|
+
);
|
|
632
|
+
return {
|
|
633
|
+
algorithm: "sha256",
|
|
634
|
+
value: [...new Uint8Array(digest)]
|
|
635
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
636
|
+
.join(""),
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
585
640
|
function normalizeBaseUrl(baseUrl: string): string {
|
|
586
641
|
return baseUrl.replace(/\/+$/, "");
|
|
587
642
|
}
|
|
588
643
|
|
|
644
|
+
function normalizeContentType(contentType: string): string {
|
|
645
|
+
return contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
646
|
+
}
|
|
647
|
+
|
|
589
648
|
function actionUrl(
|
|
590
649
|
baseUrl: string,
|
|
591
650
|
uploadName: string,
|