@fabrika/app 0.8.2 → 0.9.0-cloudflare.2
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/package.json +40 -24
- package/skill/SKILL.md +29 -3
- package/skill/references/app-setup.md +29 -11
- package/skill/references/dev.md +2 -1
- package/skill/references/endpoint.md +12 -3
- package/skill/references/errors.md +39 -35
- package/skill/references/files.md +4 -2
- package/skill/references/jobs.md +9 -0
- package/skill/references/table-and-migration.md +19 -2
- package/skill/references/tests.md +33 -1
- package/skill/references/webhooks-links-connectors.md +1 -1
- package/src/build-recipe.ts +198 -0
- package/src/build.ts +60 -4
- package/src/bun.ts +33 -8
- package/src/cloudflare/application.ts +352 -0
- package/src/cloudflare/bindings.ts +159 -0
- package/src/cloudflare/names.ts +16 -0
- package/src/cloudflare/ops.ts +246 -0
- package/src/cloudflare/r2-files.ts +172 -0
- package/src/cloudflare.ts +99 -146
- package/src/contract/app-id.ts +5 -0
- package/src/contract/artifact-schema.ts +8 -1
- package/src/contract/build.ts +68 -11
- package/src/contract/compile.ts +1 -0
- package/src/contract/context.ts +9 -0
- package/src/contract/declaration.ts +2 -0
- package/src/contract/define.ts +28 -2
- package/src/contract/group.ts +10 -1
- package/src/contract/kit.ts +6 -2
- package/src/contract/platform.ts +3 -2
- package/src/contract/vocabulary.ts +26 -8
- package/src/dev/changes.ts +35 -0
- package/src/dev/contract-digest.ts +17 -0
- package/src/dev/index.ts +11 -0
- package/src/dev/launcher.ts +713 -0
- package/src/dev/output.ts +72 -0
- package/src/dev/wire.ts +136 -0
- package/src/disk-assets.ts +8 -3
- package/src/files/archive.ts +2 -2
- package/src/files/job-names.ts +12 -0
- package/src/files/kinds.ts +19 -7
- package/src/files/scan.ts +3 -4
- package/src/files/streams.ts +3 -1
- package/src/implement/anonymous.ts +11 -1
- package/src/implement/boot.ts +70 -7
- package/src/implement/bounded.ts +21 -2
- package/src/implement/capability.ts +18 -3
- package/src/implement/dispatch.ts +251 -147
- package/src/implement/egress.ts +341 -8
- package/src/implement/failures.ts +17 -1
- package/src/implement/faults.ts +1 -0
- package/src/implement/index.ts +1 -0
- package/src/implement/invocation.ts +18 -9
- package/src/implement/principal.ts +25 -2
- package/src/implement/terminals.ts +93 -0
- package/src/implement/workload.ts +24 -13
- package/src/jobs/report.ts +5 -1
- package/src/jobs/runner.ts +40 -19
- package/src/jobs/schedule.ts +7 -1
- package/src/jobs/scrub.ts +14 -1
- package/src/spa-fallback.ts +12 -0
- package/src/testing/endpoint-access.ts +307 -152
- package/src/testing/isolation.ts +8 -1
- package/src/testing/kit.ts +20 -2
- package/src/testing/proxy.ts +5 -5
- package/types/src/agent-skill.d.ts +29 -0
- package/types/src/build-recipe.d.ts +70 -0
- package/types/src/build.d.ts +29 -0
- package/types/src/bun.d.ts +66 -0
- package/types/src/client/caller.d.ts +19 -0
- package/types/src/client/contract.d.ts +131 -0
- package/types/src/client/error-reporting.d.ts +66 -0
- package/types/src/client/index.d.ts +4 -0
- package/types/src/client/rpc.d.ts +14 -0
- package/types/src/client/share.d.ts +2 -0
- package/types/src/client/transport.d.ts +42 -0
- package/types/src/cloudflare/application.d.ts +42 -0
- package/types/src/cloudflare/bindings.d.ts +33 -0
- package/types/src/cloudflare/names.d.ts +11 -0
- package/types/src/cloudflare/ops.d.ts +46 -0
- package/types/src/cloudflare/r2-files.d.ts +9 -0
- package/types/src/cloudflare.d.ts +42 -0
- package/types/src/contract/app-id.d.ts +2 -0
- package/types/src/contract/artifact-schema.d.ts +8 -0
- package/types/src/contract/attachment.d.ts +41 -0
- package/types/src/contract/build.d.ts +12 -0
- package/types/src/contract/compile.d.ts +11 -0
- package/types/src/contract/connect.d.ts +51 -0
- package/types/src/contract/context.d.ts +510 -0
- package/types/src/contract/declaration.d.ts +307 -0
- package/types/src/contract/defaults.d.ts +208 -0
- package/types/src/contract/define.d.ts +376 -0
- package/types/src/contract/file.d.ts +44 -0
- package/types/src/contract/group.d.ts +282 -0
- package/types/src/contract/handled.d.ts +18 -0
- package/types/src/contract/index.d.ts +19 -0
- package/types/src/contract/kit.d.ts +211 -0
- package/types/src/contract/platform.d.ts +9 -0
- package/types/src/contract/problems.d.ts +14 -0
- package/types/src/contract/redirect.d.ts +25 -0
- package/types/src/contract/schema-from-shape.d.ts +10 -0
- package/types/src/contract/schema-shape.d.ts +5 -0
- package/types/src/contract/snapshot-source.d.ts +15 -0
- package/types/src/contract/token.d.ts +58 -0
- package/types/src/contract/vocabulary.d.ts +32 -0
- package/types/src/contract/webhook-binding.d.ts +24 -0
- package/types/src/contract/webhook-scheme.d.ts +23 -0
- package/types/src/dev/changes.d.ts +7 -0
- package/types/src/dev/contract-digest.d.ts +1 -0
- package/types/src/dev/index.d.ts +2 -0
- package/types/src/dev/launcher.d.ts +32 -0
- package/types/src/dev/output.d.ts +28 -0
- package/types/src/dev/wire.d.ts +126 -0
- package/types/src/disk-assets.d.ts +6 -0
- package/types/src/errors.d.ts +46 -0
- package/types/src/fetch.d.ts +7 -0
- package/types/src/files/archive.d.ts +26 -0
- package/types/src/files/clamscan.d.ts +32 -0
- package/types/src/files/handle.d.ts +106 -0
- package/types/src/files/job-names.d.ts +9 -0
- package/types/src/files/kinds.d.ts +101 -0
- package/types/src/files/scan.d.ts +78 -0
- package/types/src/files/sniff.d.ts +7 -0
- package/types/src/files/streams.d.ts +17 -0
- package/types/src/files/treatment.d.ts +18 -0
- package/types/src/from-row.d.ts +20 -0
- package/types/src/honeypot.d.ts +2 -0
- package/types/src/implement/anonymous.d.ts +28 -0
- package/types/src/implement/attachment.d.ts +14 -0
- package/types/src/implement/boot.d.ts +120 -0
- package/types/src/implement/bounded.d.ts +32 -0
- package/types/src/implement/browser-config.d.ts +19 -0
- package/types/src/implement/capability.d.ts +144 -0
- package/types/src/implement/challenge.d.ts +75 -0
- package/types/src/implement/client-address.d.ts +11 -0
- package/types/src/implement/connect.d.ts +67 -0
- package/types/src/implement/crashes.d.ts +31 -0
- package/types/src/implement/dispatch.d.ts +221 -0
- package/types/src/implement/egress.d.ts +49 -0
- package/types/src/implement/environment.d.ts +14 -0
- package/types/src/implement/failures.d.ts +39 -0
- package/types/src/implement/faults.d.ts +45 -0
- package/types/src/implement/headers.d.ts +7 -0
- package/types/src/implement/implement.d.ts +58 -0
- package/types/src/implement/index.d.ts +25 -0
- package/types/src/implement/invocation.d.ts +125 -0
- package/types/src/implement/membership.d.ts +201 -0
- package/types/src/implement/multipart.d.ts +36 -0
- package/types/src/implement/params.d.ts +7 -0
- package/types/src/implement/principal.d.ts +57 -0
- package/types/src/implement/project.d.ts +11 -0
- package/types/src/implement/public.d.ts +61 -0
- package/types/src/implement/serve.d.ts +25 -0
- package/types/src/implement/terminals.d.ts +33 -0
- package/types/src/implement/tokens.d.ts +49 -0
- package/types/src/implement/types.d.ts +243 -0
- package/types/src/implement/urls.d.ts +15 -0
- package/types/src/implement/verifiers.d.ts +19 -0
- package/types/src/implement/webhook.d.ts +55 -0
- package/types/src/implement/workload.d.ts +56 -0
- package/types/src/implement/xlsx.d.ts +6 -0
- package/types/src/index.d.ts +12 -0
- package/types/src/jobs/exception.d.ts +40 -0
- package/types/src/jobs/mail.d.ts +67 -0
- package/types/src/jobs/report.d.ts +56 -0
- package/types/src/jobs/runner.d.ts +154 -0
- package/types/src/jobs/schedule.d.ts +17 -0
- package/types/src/jobs/scrub.d.ts +12 -0
- package/types/src/jobs/types.d.ts +98 -0
- package/types/src/react/caller.d.ts +30 -0
- package/types/src/react/errors.d.ts +40 -0
- package/types/src/react/hooks.d.ts +110 -0
- package/types/src/react/index.d.ts +5 -0
- package/types/src/react/provider.d.ts +58 -0
- package/types/src/react/store.d.ts +68 -0
- package/types/src/react/testing.d.ts +28 -0
- package/types/src/react/types.d.ts +33 -0
- package/types/src/rpc/builder.d.ts +52 -0
- package/types/src/rpc/contract.d.ts +23 -0
- package/types/src/rpc/dispatcher.d.ts +11 -0
- package/types/src/rpc/types.d.ts +62 -0
- package/types/src/service/define.d.ts +42 -0
- package/types/src/service/index.d.ts +3 -0
- package/types/src/service/serve.d.ts +28 -0
- package/types/src/spa-fallback.d.ts +2 -0
- package/types/src/standard-schema.d.ts +55 -0
- package/types/src/testing/caller.d.ts +95 -0
- package/types/src/testing/challenge.d.ts +11 -0
- package/types/src/testing/dev.d.ts +97 -0
- package/types/src/testing/endpoint-access.d.ts +131 -0
- package/types/src/testing/index.d.ts +12 -0
- package/types/src/testing/isolation.d.ts +3 -0
- package/types/src/testing/kit.d.ts +187 -0
- package/types/src/testing/membership.d.ts +132 -0
- package/types/src/testing/policy-isolation.d.ts +113 -0
- package/types/src/testing/preload.d.ts +1 -0
- package/types/src/testing/proxy.d.ts +40 -0
- package/types/src/testing/runner.d.ts +8 -0
- package/types/src/testing/sample-file.d.ts +5 -0
- package/types/src/testing/status.d.ts +4 -0
- package/types/src/undumpable.d.ts +5 -0
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from '@fabrika/auth-core'
|
|
17
17
|
import type { TokenVerifier } from '@fabrika/jwks'
|
|
18
18
|
import { z } from 'zod/v4'
|
|
19
|
+
import { deadline } from './bounded'
|
|
19
20
|
|
|
20
21
|
export type WorkloadKindOf = OwnWorkloadKind
|
|
21
22
|
|
|
@@ -39,20 +40,30 @@ export function issuerWorkloadTokens(
|
|
|
39
40
|
): WorkloadTokenSource {
|
|
40
41
|
return {
|
|
41
42
|
async exchange(input) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
const timer = deadline(EXCHANGE_TIMEOUT_MS)
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetcher(
|
|
46
|
+
new Request(new URL(WORKLOAD_TOKEN_PATH, issuer), {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
headers: { authorization: `Bearer ${input.key}`, 'content-type': 'application/json' },
|
|
49
|
+
body: JSON.stringify({ app: input.app }),
|
|
50
|
+
redirect: 'manual',
|
|
51
|
+
signal: timer.signal,
|
|
52
|
+
}),
|
|
53
|
+
)
|
|
54
|
+
if (response.status !== 200) {
|
|
55
|
+
// Nothing reads a refusal's body, and once the deadline is cleared nothing else would end its stream.
|
|
56
|
+
await response.body?.cancel().catch(() => {})
|
|
57
|
+
throw new Error(`IAM answered the workload token exchange with ${response.status}`)
|
|
58
|
+
}
|
|
59
|
+
const parsed = mintResultSchema.safeParse(await response.json())
|
|
60
|
+
if (!parsed.success) {
|
|
61
|
+
throw new Error(`IAM answered the workload token exchange with ${response.status}`)
|
|
62
|
+
}
|
|
63
|
+
return parsed.data
|
|
64
|
+
} finally {
|
|
65
|
+
timer.clear()
|
|
54
66
|
}
|
|
55
|
-
return parsed.data
|
|
56
67
|
},
|
|
57
68
|
}
|
|
58
69
|
}
|
package/src/jobs/report.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// groups a framework report by its fingerprint and raises its new-issue and regression alerts, so each kind of
|
|
4
4
|
// failure becomes one issue. What was thrown travels scrubbed as the report's cause chain (ADR-0059).
|
|
5
5
|
|
|
6
|
+
import { deadline } from '../implement/bounded'
|
|
6
7
|
import { exceptionChain, type ReportedException } from './exception'
|
|
7
8
|
import type { Scrubber } from './scrub'
|
|
8
9
|
|
|
@@ -136,6 +137,7 @@ export function crashEnvelope(kind: CrashKind, error: unknown, options: Envelope
|
|
|
136
137
|
|
|
137
138
|
async function deliver(options: OperationsReporterOptions, body: string): Promise<void> {
|
|
138
139
|
const send = options.fetch ?? ((request: Request) => fetch(request))
|
|
140
|
+
const timer = deadline(DELIVERY_TIMEOUT_MS)
|
|
139
141
|
const request = new Request(options.target.envelopeUrl, {
|
|
140
142
|
method: 'POST',
|
|
141
143
|
headers: {
|
|
@@ -143,7 +145,7 @@ async function deliver(options: OperationsReporterOptions, body: string): Promis
|
|
|
143
145
|
'x-sentry-auth': `Sentry sentry_version=7, sentry_key=${options.target.publicKey}`,
|
|
144
146
|
},
|
|
145
147
|
body,
|
|
146
|
-
signal:
|
|
148
|
+
signal: timer.signal,
|
|
147
149
|
})
|
|
148
150
|
try {
|
|
149
151
|
const response = await send(request)
|
|
@@ -154,6 +156,8 @@ async function deliver(options: OperationsReporterOptions, body: string): Promis
|
|
|
154
156
|
} catch {
|
|
155
157
|
// The error may quote the ingest URL with its key.
|
|
156
158
|
console.error('fabrika: a report to Operations could not be delivered')
|
|
159
|
+
} finally {
|
|
160
|
+
timer.clear()
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
163
|
|
package/src/jobs/runner.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { type ContractArtifact, JOB_SITE_PREFIX, type JobArtifact, type JobMaile
|
|
|
10
10
|
import type { CapabilityAuthority } from '@fabrika/db/capability'
|
|
11
11
|
import { type DataAccess, type FrameworkTables, type JobLeases, type LeaseFence, LeaseLostError, type Session } from '@fabrika/db/framework'
|
|
12
12
|
import { EmailDeliveryError, type EmailSenderCapabilities, mayRetryDelivery } from '@fabrika/email'
|
|
13
|
+
import { uuidv7 } from '@fabrika/platform'
|
|
13
14
|
import { NotFoundError } from '../errors'
|
|
14
15
|
import { FileWork, type StoredFileKinds, storedFilesHandle } from '../files/kinds'
|
|
15
16
|
import {
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
runInRequest,
|
|
22
23
|
type Within,
|
|
23
24
|
} from '../implement/capability'
|
|
24
|
-
import { runAsSite } from '../implement/egress'
|
|
25
|
+
import { type PublicEgressRules, runAsSite, siteEgress } from '../implement/egress'
|
|
25
26
|
import { failureCode, type FailureReason, failureReason } from '../implement/failures'
|
|
26
27
|
import { ServerFault } from '../implement/faults'
|
|
27
28
|
import type { ContractDatabase } from '../implement/implement'
|
|
@@ -117,6 +118,8 @@ export interface FrameworkJob {
|
|
|
117
118
|
export interface ContractJobs {
|
|
118
119
|
/** The contract's `c.cron` jobs; `run` also takes the name of a framework job the contract needs. */
|
|
119
120
|
readonly names: readonly string[]
|
|
121
|
+
/** Every job bound to run, the framework's included, with its schedule: what a minute tick runs when due. */
|
|
122
|
+
readonly scheduled: readonly { readonly name: string; readonly schedule: CronSchedule }[]
|
|
120
123
|
run(name: string, options?: JobRunOptions): Promise<JobRunResult>
|
|
121
124
|
/**
|
|
122
125
|
* Registers every job (its first-seen time), reports a run that crashed and a job that has not succeeded within
|
|
@@ -185,6 +188,8 @@ interface RunnerContext {
|
|
|
185
188
|
/** The environment's signed tokens: `ctx.tokens` of a job that declares `signs`. */
|
|
186
189
|
readonly tokens: SignedTokens | null
|
|
187
190
|
readonly url: AppUrl
|
|
191
|
+
/** What a job that declares `egress: 'public'` reaches; such a job reaches nothing without it. */
|
|
192
|
+
readonly publicEgress: PublicEgressRules | null
|
|
188
193
|
}
|
|
189
194
|
|
|
190
195
|
/** Where `fabrika app dev` shows what a handler threw, on the developer's own machine; nothing else sets one. */
|
|
@@ -224,6 +229,8 @@ export interface JobBindingInput {
|
|
|
224
229
|
readonly tokens?: SignedTokens
|
|
225
230
|
/** `ctx.url`: absolute links to the application's own procedures; without it every link is refused. */
|
|
226
231
|
readonly url?: AppUrl
|
|
232
|
+
/** The rules of public egress, which `boot` reads from the environment; without them a public job reaches nothing. */
|
|
233
|
+
readonly publicEgress?: PublicEgressRules
|
|
227
234
|
}
|
|
228
235
|
|
|
229
236
|
interface BoundFrameworkJob {
|
|
@@ -323,11 +330,13 @@ export function bindJobs(input: JobBindingInput, problems: string[]): BoundJobs
|
|
|
323
330
|
mail: input.mail,
|
|
324
331
|
tokens: input.tokens ?? null,
|
|
325
332
|
url: input.url ?? appUrl(input.artifact, null),
|
|
333
|
+
publicEgress: input.publicEgress ?? null,
|
|
326
334
|
// A job's kicks run only after the job is bound, so `kick` below is defined by then.
|
|
327
335
|
kick: (name: string) => kick(name),
|
|
328
336
|
})
|
|
329
337
|
const jobs: ContractJobs = Object.freeze({
|
|
330
338
|
names,
|
|
339
|
+
scheduled: Object.freeze([...bound.values(), ...frameworkJobs.values()].map(({ name, schedule }) => ({ name, schedule }))),
|
|
331
340
|
run(name: string, options: JobRunOptions = {}): Promise<JobRunResult> {
|
|
332
341
|
const own = frameworkJobs.get(name)
|
|
333
342
|
if (own !== undefined && framework !== null) {
|
|
@@ -432,7 +441,7 @@ function jobLinks(where: string, site: string, declaration: SiteDeclaration | nu
|
|
|
432
441
|
return { name, prefix: mount.prefix, dimension: mount.dimension, issuedBy: mount.issuedBy }
|
|
433
442
|
}
|
|
434
443
|
|
|
435
|
-
const attemptId = (): string =>
|
|
444
|
+
const attemptId = (): string => uuidv7()
|
|
436
445
|
|
|
437
446
|
/** Exponential from one second, capped at five minutes, with half of it jittered. */
|
|
438
447
|
function backoffMs(attempt: number, random: number): number {
|
|
@@ -694,7 +703,7 @@ async function attemptOnce(
|
|
|
694
703
|
if (site === null) {
|
|
695
704
|
await job.handler({ ctx })
|
|
696
705
|
} else {
|
|
697
|
-
await runAsSite(`job '${job.name}'`, site.egress, async () => await job.handler({ ctx }))
|
|
706
|
+
await runAsSite(`job '${job.name}'`, siteEgress(site.egress, context.publicEgress), async () => await job.handler({ ctx }))
|
|
698
707
|
}
|
|
699
708
|
kicked.push(...kicks.kicked())
|
|
700
709
|
}).then(() => null, (error: unknown) => {
|
|
@@ -898,25 +907,37 @@ function kitJobContext(
|
|
|
898
907
|
db: siteData(reads, (action) => reads.app(app(action))),
|
|
899
908
|
sql: site.sql === null ? undeclaredSql(job, 'job') : jobSqlOutsideTransaction(job),
|
|
900
909
|
files: putAndOpen(boundTo(frame, () => false, 'ctx.files')),
|
|
901
|
-
transaction: async <R>(
|
|
910
|
+
transaction: async <R>(body: (tx: object) => Promise<R>): Promise<R> => {
|
|
902
911
|
// Outside its attempt there is no lease to fence the transaction with, so it never opens.
|
|
903
912
|
boundTo(frame, () => false)()
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const
|
|
911
|
-
|
|
912
|
-
|
|
913
|
+
// Its own files: a rollback releases only what this transaction staged, never a file of a concurrent one.
|
|
914
|
+
const own = new FileWork(frame, frame.id)
|
|
915
|
+
let rolledBack = false
|
|
916
|
+
try {
|
|
917
|
+
return await data.transaction(async (session) => {
|
|
918
|
+
let open = true
|
|
919
|
+
const writes = jobHandles(frame, data, site, principal, 'mutation', async () => session, () => !open, undefined, binding)
|
|
920
|
+
try {
|
|
921
|
+
const sql = site.sql === null ? undeclaredSql(job, 'job', 'tx.sql') : jobSql(frame, data, site.sql, session, () => !open)
|
|
922
|
+
// Located in this transaction, so an object it wrote is found before the commit.
|
|
923
|
+
const located = (action: string, dimension: string, id: string) => {
|
|
924
|
+
boundTo(frame, () => !open, 'tx.ref')()
|
|
925
|
+
return ref(action, dimension, id, session)
|
|
926
|
+
}
|
|
927
|
+
const files = storedFilesHandle(kinds, own, { where: job, check: boundTo(frame, () => !open, 'tx.files'), writes: true })
|
|
928
|
+
return await body(Object.freeze({ db: siteData(writes, (action) => writes.app(app(action))), sql, files, ref: located }))
|
|
929
|
+
} catch (error) {
|
|
930
|
+
rolledBack = true
|
|
931
|
+
throw error
|
|
932
|
+
} finally {
|
|
933
|
+
open = false
|
|
913
934
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
}
|
|
935
|
+
})
|
|
936
|
+
} catch (error) {
|
|
937
|
+
// A transaction the work threw out of is a known rollback; a failed commit is not, and the sweep decides.
|
|
938
|
+
if (rolledBack) await kinds?.release(own)
|
|
939
|
+
throw error
|
|
940
|
+
}
|
|
920
941
|
},
|
|
921
942
|
}
|
|
922
943
|
}
|
package/src/jobs/schedule.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The five-field cron expressions of `c.cron`, read in UTC: which minutes a job is due, so the runner can tell how
|
|
2
|
-
// long ago it should last have succeeded.
|
|
2
|
+
// long ago it should last have succeeded. On Bun the crontab triggers a run; on Cloudflare the application's object
|
|
3
|
+
// runs, at each minute tick, the jobs `isDueAt` that minute.
|
|
3
4
|
|
|
4
5
|
interface Field {
|
|
5
6
|
readonly min: number
|
|
@@ -96,6 +97,11 @@ function dayMatches(schedule: CronSchedule, day: Date): boolean {
|
|
|
96
97
|
return schedule.eitherDay ? byMonth || byWeek : byMonth && byWeek
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
/** Whether the schedule is due in the UTC minute that holds `at`: what a Worker's minute tick runs (ADR-0065). */
|
|
101
|
+
export function isDueAt(schedule: CronSchedule, at: Date): boolean {
|
|
102
|
+
return schedule.minutes.has(at.getUTCMinutes()) && schedule.hours.has(at.getUTCHours()) && dayMatches(schedule, at)
|
|
103
|
+
}
|
|
104
|
+
|
|
99
105
|
const descending = (values: ReadonlySet<number>): number[] => [...values].sort((left, right) => right - left)
|
|
100
106
|
|
|
101
107
|
/** The latest `count` due minutes at or before `at`, newest first; fewer when the schedule is that rare. */
|
package/src/jobs/scrub.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
// What an error report must not carry (ADR-0059): the values of the application's secrets, and anything shaped like a
|
|
2
2
|
// credential — a JWT, an `Authorization` scheme or header, a URL's userinfo, a sensitive query parameter.
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
FABRIKA_APPLICATION_OBJECT,
|
|
6
|
+
FABRIKA_APPLICATION_OBJECTS,
|
|
7
|
+
FABRIKA_CONTRACT_DIGEST,
|
|
8
|
+
FABRIKA_IAM_APP_ID,
|
|
9
|
+
FABRIKA_IAM_ISSUER,
|
|
10
|
+
FABRIKA_OPS_KEY_DIGESTS,
|
|
11
|
+
PLATFORM_RECEIVERS,
|
|
12
|
+
platformTransportVariables,
|
|
13
|
+
} from '@fabrika/auth-core'
|
|
5
14
|
import type { CapturedEnvironment } from '../implement/environment'
|
|
6
15
|
|
|
7
16
|
export const REDACTED = '[redacted]'
|
|
@@ -21,6 +30,10 @@ const PUBLIC_FRAMEWORK_VARIABLES: ReadonlySet<string> = new Set([
|
|
|
21
30
|
FABRIKA_CONTRACT_DIGEST,
|
|
22
31
|
FABRIKA_IAM_APP_ID,
|
|
23
32
|
FABRIKA_IAM_ISSUER,
|
|
33
|
+
// Digests of the ops keys, which are no credential; the object names are words (`application`) a message carries.
|
|
34
|
+
FABRIKA_OPS_KEY_DIGESTS,
|
|
35
|
+
FABRIKA_APPLICATION_OBJECT,
|
|
36
|
+
FABRIKA_APPLICATION_OBJECTS,
|
|
24
37
|
...PLATFORM_RECEIVERS.map((receiver) => platformTransportVariables(receiver).url),
|
|
25
38
|
])
|
|
26
39
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Which request an SPA mount answers with its `index.html` when it names no file: a route of the page, not a missing
|
|
2
|
+
// asset. Both asset servers apply it (`diskAssets`, `bindingAssets`), so a missing script stays a 404 and never arrives
|
|
3
|
+
// as an HTML page with a 200.
|
|
4
|
+
|
|
5
|
+
import { rawRequestTarget } from '@fabrika/auth-core'
|
|
6
|
+
|
|
7
|
+
/** A GET or HEAD whose last path segment has no extension, or one that asks for HTML (a navigation to `/notes/v1.2`). */
|
|
8
|
+
export function isPageRoute(request: Request): boolean {
|
|
9
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') return false
|
|
10
|
+
const last = rawRequestTarget(request.url).pathname.split('/').pop() ?? ''
|
|
11
|
+
return !last.includes('.') || (request.headers.get('Accept') ?? '').includes('text/html')
|
|
12
|
+
}
|