تخطَّ إلى المحتوى

Helpers

هذا المحتوى غير متوفر بلغتك بعد.

The @framework/support package provides small, typed helpers inspired by Laravel’s helpers. Unlike Laravel they are imported functions, not globals — and the context-aware ones resolve the current request or job through the framework’s runtime, so there is nothing to pass by hand:

import { config, env, abort, logger, correlationId } from '@framework/support';

Generated applications already depend on @framework/support. Inside requests and jobs the context helpers see the real actor and correlation id; anywhere else they fall back to a standalone default.

Laravel This framework Notes
config('app.name') config('app.name') Reads the validated, frozen config; throws before boot
env('REDIS_URL') env('REDIS_URL', 'redis://…') Process env always wins over files
abort(404) / abort_if() abort(404) / abort_if(cond, 403) Throws the catalogued HttpError envelope
logger() logger('orders').info(…) JSON lines with correlation id + actor
retry() retry(work, { attempts, backoffMs }) Exponential backoff (queues use defineJob)
now() / Str::uuid() nowIso() / uuid() ISO-8601 and RFC 4122 v4
tap() / value() tap() / value() Same semantics
Auth user currentActor() / execution() Scope-aware; anonymous outside requests/jobs

Dot-path access into the booted application’s validated config — the same frozen object the container exposes as CONFIG:

config('http.port'); // 3000
config('database.url'); // postgres://…
config('app.debug', false); // with fallback
config('no.such.path', 'fallback'); // 'fallback'

Throws RUNTIME_NOT_BOOTED when no Application was constructed — scripts should load config explicitly with loadAppConfig() instead.

Process-environment lookup. Process env always wins over .env files (the framework’s documented precedence); an empty string counts as missing:

env('REDIS_URL'); // string | undefined
env('QUEUE_NAME', 'default'); // string

The HTTP kernel and the queue worker bind the current execution to the async chain, so these work anywhere below a request or a job:

Helper Returns
execution() Full context: kind (request/job/command), correlationId, actor
correlationId() The current request/job correlation id ('standalone' outside one)
currentActor() { id, type } of who is acting (anonymous outside a scope)
await bus.publish(createDomainEvent('orders', 'created', payload, execution()));
logger().info('reserving stock', { orderId }); // log lines carry correlationId

Throws the framework’s HttpError, which the error handler renders as the standard JSON envelope. Accepts an HTTP status or a catalog code:

abort(404); // NOT_FOUND
abort('VALIDATION_FAILED', 'bad input'); // 422 with a message

abort_if(condition, …) / abort_unless(condition, …)

Section titled “abort_if(condition, …) / abort_unless(condition, …)”

Guard-clause variants:

abort_unless(actor.id === order.ownerId, 403);
abort_if(order.status === 'shipped', 409, 'Shipped orders cannot change');

One JSON object per line on stderr, carrying time, level, scope, message, the current correlationId and actorId — logs from one request (and the jobs it dispatched) grep together:

logger('orders').info('order created', { orderId: 'o-1' });
// {"time":"…","level":"info","scope":"orders","message":"order created",
// "correlationId":"0aca8…","actorId":"user-1","data":{"orderId":"o-1"}}

Values must be JSON-safe. Redact secrets before logging them — pass objects through redactForLogs(); CLI diagnostics redact automatically.

The global config() takes any string. For editor autocomplete on every path and exact return types, create a reader once per app with your AppConfig type (generated apps ship this as app/config/helpers.ts):

import { createConfigReader } from '@framework/support';
import type { AppConfig } from '../../config';
export const { get: config, getWithDefault: configOrDefault } = createConfigReader<AppConfig>();
config('http.port'); // type: number — 'http.' suggests every known key
config('app.name'); // type: string
configOrDefault('owner', 'system'); // fallback keeps the NonNullable type

A wrong path or mismatched fallback is a compile-time error, and the runtime still reads the same validated config — only the types were added.

Helper Purpose
retry(work, { attempts, backoffMs }) Exponential-backoff retry for transient failures (queue semantics belong in defineJob)
sleep(ms) Promise-based delay
tap(value, fn) Run fn for its side effect, return value
value(input) Resolve input or input() (lazy values)
uuid() RFC 4122 v4 UUID
nowIso() Current time as ISO-8601 (the framework’s JSON timestamp format)
redactForLogs(obj) Mask secret-looking keys (apiKey, password, …)
isSecret(key) Whether a key name looks like a secret
const reachable = await retry(() => pingService(), { attempts: 3, backoffMs: 200 });
const order = tap(await orders.find(id), (o) => logger().info('loaded', { id: o.id }));