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

Enterprise الإصدار

التدفق (Stream): معالجة مهام المستندات

يحوّل ⁨NextPDF\Enterprise\Stream\DocumentJobStreamProcessor⁩ تدفقاً من بيانات العرض (render manifests) إلى نتائج دائمة وقابلة للمساءلة. يستهلك ⁨iterable<RenderManifest>⁩ بوصفه مولّداً (generator)، ويعرض نوافذ محدودة عبر محرك عرض ⁨Pro⁩، ويُنهي كل مهمة وفق ترتيب المصدر. تنتهي كل مهمة في حالة نهائية واحدة تماماً: المُخرَج مُثبَّت، أو مُعترَف به على أنه مُثبَّت مسبقاً، أو مُحال إلى الرسائل الميتة. يُسجَّل التقدّم في نقاط تفتيش، بحيث يستأنف التشغيل المتعطّل دون إعادة نشر أي شيء.

تنقسم حكاية ⁨Stream⁩ عبر إصدارَين، والانقسام مقصود. يوفّر ⁨Pro⁩ محرك العرض الدائم والمتزامن ومخازن نظام الملفات المحلية أحادية المضيف — النصف داخل العملية. ويوفّر ⁨Enterprise⁩ معالج تدفق مهام المستندات هذا إضافة إلى الأجزاء التي تعبر حدود المضيفات: مُثبِّت تخزين الكائنات (⁨ObjectStorageCommitter⁩) وصندوق الصادر الدائم للأحداث النهائية (⁨FilesystemOutboxEmitter⁩). وتذكر صفحة ⁨Pro Stream⁩ الحدّ نفسه من جانبها.

تُشحن هذه القدرة ضمن ⁨NextPDF Enterprise⁩ (⁨nextpdf/enterprise⁩) وتُفعَّل عبر مظروف ترخيص من فئة ⁨Enterprise⁩. ولا يُحمّل أي نشر يفتقر إلى ذلك الاستحقاق أصنافَ هذه القدرة. قارن بين الإصدارات واحصل على ترخيص.

Terminal window
composer require nextpdf/enterprise

تقع الأصناف الواردة في هذه الصفحة ضمن ⁨NextPDF\Enterprise\Stream⁩ و⁨NextPDF\Enterprise\Stream\Storage⁩. وهي تستهلك عقود ⁨Pro⁩ المجمّدة في ⁨NextPDF\Pro\Stream⁩ — واجهات المحرك، والمُثبِّت، ونقطة التفتيش، والتكافؤ (idempotency)، وإعادة المحاولة، والرسائل الميتة (dead-letter).

مهمة المعالج هي دلالات التسليم، لا العرض. فهو يجمّع تدفق الـ ⁨manifest⁩ في نوافذ حسب إزاحة المصدر لا تتجاوز حجم دفعة المحرك. تُعرَض كل نافذة عبر ⁨RenderEngineInterface::renderBatch()⁩، مع إعادة محاولة محدودة وحتمية لمُهلات كل عنصر. ثم يُنهى كل عنصر وفق ترتيب إزاحة المصدر إلى نتيجة نهائية.

حدّ المرة-الواحدة-تماماً يكون لكل عنصر، وهو مرتكز في المُثبِّت، لا في التنسيق. التقدّم الدائم هو علامة مائية عليا لإزاحة تبدأ من 1: كل إزاحة عند نقطة التفتيش أو دونها بلغت نتيجة نهائية. ترتيب الحاجز ثابت: ثبّت البايتات، ثم قدّم العلامة المائية، ثم احفظ نقطة التفتيش، ثم أفرغ علامات التكافؤ المؤقتة، ثم أطلق الأحداث النهائية. أي عطل بين التثبيت ونقطة التفتيش يعيد التثبيت بتكافؤ عند الاستئناف، لأن المُثبِّت يقارن البصمات (digests). وأي عطل بعد نقطة التفتيش يتخطّى الإزاحة سريعاً، فلا يُنشر شيء مرتين.

ينفّذ ⁨ObjectStorageCommitter⁩ واجهة ⁨OutputCommitterInterface⁩ الخاصة بـ ⁨Pro⁩ مقابل مخزن كائنات عبر واجهة ⁨ObjectStorageClientInterface⁩ الأدنى. ويكون ⁨container⁩ الهدف هو الحاوية (bucket)، و⁨key⁩ مفتاح الكائن. إعادة تثبيت بايتات متطابقة عملية لا-تأثير لها بعد مقارنة البصمة. والبايتات المتباينة دون ⁨overwrite⁩ تُطلق تعارض ⁨SPEC-COMMIT-409⁩. ولا يُنشأ كائن جديد إلا عبر الكتابة الشرطية الذرّية ⁨putIfAbsent()⁩؛ وخسارة ذلك السباق تُشغّل حلقة محدودة لإعادة القراءة والحسم. ولذلك يصمد ضمان المرة-الواحدة-تماماً عبر الكُتّاب المتعددين بقدر ما يكون ⁨putIfAbsent()⁩ في مُهايئك كتابة شرطية حقيقية — ⁨If-None-Match: *⁩ على ⁨S3⁩، و⁨ifGenerationMatch: 0⁩ على ⁨GCS⁩. تشحن هذه الدورة الواجهة إضافة إلى ⁨NullObjectStorageClient⁩ العامل في الذاكرة؛ أما مُهايئ ⁨S3/GCS⁩ الحيّ فيوفّره المضيف.

تُغلق الأحداث النهائية الحلقة للأنظمة اللاحقة. فبعد حاجز نقطة التفتيش يحاول المعالج إطلاق ⁨JobTerminalEvent⁩ لكل مهمة مُنهاة — مُعرّفات، وحالة، وإيصال، وتفاصيل خطأ، وعدد المحاولات، ودون أي بايتات ⁨PDF⁩ إطلاقاً. ومع مُطلِق نداء ارتجاعي بسيط يكون الإطلاق مرة-على-الأكثر: قد تُتخطّى الأحداث الواقعة بعد نقطة التفتيش عند الاستئناف من عطل. ويجعل ⁨FilesystemOutboxEmitter⁩ كل حدث دائماً بمجرد تشغيل ⁨emit()⁩: فكل حدث ملف ⁨JSON⁩ ذرّي واحد يُسمّى ببصمة ⁨eventId⁩ الحتمي، فتصير إعادة الإطلاق بعد الاستئناف متكافئة، ويُسلّم المُرحِّل مرة-على-الأقل، ويزيل المستهلكون التكرار وفق ⁨eventId⁩. ويبقى حدّ واحد في كلتا الحالتين: يقع الإطلاق بعد حاجز نقطة التفتيش، فأي عطل بين ⁨checkpoint.save()⁩ و⁨emit()⁩ يتخطّى الحدث النهائي لذلك العنصر عند الاستئناف. والأنظمة اللاحقة التي تتطلّب سجلّاً كاملاً للأحداث ينبغي أن تُطابق مقابل الكائنات المُثبَّتة (المخزن هو مصدر الحقيقة)، لا مقابل صندوق الصادر وحده.

الترخيص مربوط في مسار الإخراج. يحلّ المصنع ⁨withBrandingFromLicense()⁩ استراتيجية علامة التقييم مرة واحدة لكل تشغيل انطلاقاً من الترخيص. الترخيص المدفوع يحلّ إلى تحويل مطابق (identity transform). أما ترخيص التقييم أو غياب الترخيص فيضع علامة مائية على كل مستند مُثبَّت، ويُحال إلى الرسائل الميتة أي مستند يتعذّر وسمه — فالمعالج لا يثبّت أبداً بايتات تقييم دون علامة.

القرار الحامل هو أن المرة-الواحدة-تماماً تستند إلى كائن المُثبِّت المقارَن بالبصمة والمُنشأ شرطياً — لا إلى أقفال موزّعة أو إجماع. الكتابة الشرطية لمخزن الكائنات هي البدائية الذرّية الوحيدة التي يتطلّبها التصميم، وكل ما عداها مسموح له بأن يفشل ويتعافى. ولهذا يجب أن يبقى محرك العرض خالياً من الآثار الجانبية، ولهذا تُعامَل الحالة المُفهرَسة ومخابئ إزالة التكرار داخل التشغيل بوصفها تسريعات قابلة لإعادة الحساب، ولهذا يُجهض التثبيت الغامض التشغيلَ بدل التخمين: فمسار الاستئناف يتقارب عبر مقارنة البصمة نفسها. وهو أيضاً سبب كون كاتب-واحد لكل ⁨runId⁩ متطلَّباً مُعلَناً لا حجزاً مفروضاً — إذ يبقى مخزن نقاط التفتيش بسيطاً عن قصد، وتبقى طبقة التثبيت شبكة الأمان.

خلفية التصميم: توليد المستندات بأحجام كبيرة.

ينبغي للمضيفين البناء عبر المصنع، كي لا يبقى ضبط الترخيص-إلى-العلامة غير مربوط أبداً:

public static function withBrandingFromLicense(
RenderEngineInterface $engine,
OutputCommitterInterface $committer,
IdempotencyStoreInterface $idempotency,
CheckpointStoreInterface $checkpoints,
KeyedStateStoreInterface $state,
DeadLetterStoreInterface $deadLetters,
RetryPolicy $retryPolicy,
ClockInterface $clock,
EntitlementEvaluator $entitlementEvaluator,
?LicenseKey $license,
?StreamProcessorProbe $probe = null,
?JobCompletionEmitterInterface $emitter = null,
?BrandingApplicator $brandingApplicator = null,
): self

$clock⁩ هو ⁨Symfony\Component\Clock\ClockInterface⁩ (يمرّ تراجع إعادة المحاولة بالنوم عبره). وترخيص ⁨null⁩ يحلّ بالفشل-المُغلق إلى علامة التقييم.

نقطة الدخول الوحيدة تعالج تشغيلاً واحداً وتُعيد عدّاداته:

public function process(iterable $manifests, StreamProcessorConfig $config): ProcessingSummary

يرمي أو يفشل بـ:NextPDF\Enterprise\Stream\Exception\StreamProcessorException⁩ عند إخفاق شروط الأمان ضد الأعطال (تشغيل ⁨crashSafe⁩ مع متعاونين غير دائمين) أو عندما يكون التثبيت غامضاً؛ و⁨InvalidArgumentException⁩ عندما يتجاوز ⁨windowSize⁩ قيمة ⁨maxBatchSize()⁩ للمحرك.

public function __construct(
public string $runId,
int $windowSize = 32,
int $checkpointIntervalJobs = 100,
public bool $crashSafe = true,
public bool $emitSkippedCompletions = false,
)

يرمي أو يفشل بـ:InvalidArgumentException⁩ عندما يكون ⁨windowSize⁩ أو ⁨checkpointIntervalJobs⁩ دون 1. و⁨$runId⁩ هو مُعرّف التشغيل المستقر أحادي-الكاتب الذي يُفهرِس الاستئناف من نقطة التفتيش.

public function __construct(
private ObjectStorageClientInterface $client,
private string $scheme,
private ClockInterface $clock,
) {}

$scheme⁩ يُسمّي مخطّط الهدف الذي يخدمه هذا المُثبِّت (مثل ⁨s3⁩ أو ⁨gcs⁩)؛ و⁨$clock⁩ هنا هو ⁨Psr\Clock\ClockInterface⁩.

public function commit(
string $jobId,
OutputObjectKey $target,
string $bytes,
string $sha256,
bool $overwrite = false,
): CommitReceipt

يرمي أو يفشل بـ:UnsupportedTargetException⁩ عند عدم تطابق المخطّط؛ و⁨RenderManifestException⁩ عندما لا يكون مفتاح الهدف آمناً نسبةً إلى الحاوية؛ و⁨CommitIntegrityException⁩ عندما لا تطابق بصمة ⁨sha-256⁩ المُعلَنة البايتات؛ و⁨OutputCommitConflictException⁩ (⁨SPEC-COMMIT-409⁩) عند البايتات المتباينة دون ⁨overwrite⁩؛ و⁨RuntimeException⁩ عندما يتعذّر تقارُب سباق الإنشاء بعد 5 محاولات تحت التعديل المتزامن.

سطح المُهايئ الأدنى الذي يُنفّذه تكامل ⁨S3/GCS⁩ حيّ:

public function shaOf(string $bucket, string $key): ?string;
public function put(string $bucket, string $key, string $bytes, string $sha256): void;
public function putIfAbsent(string $bucket, string $key, string $bytes, string $sha256): bool;

يجب أن يكون ⁨putIfAbsent()⁩ إنشاءً شرطياً ذرّياً حقيقياً (⁨If-None-Match: *⁩ على ⁨S3⁩، و⁨ifGenerationMatch: 0⁩ على ⁨GCS⁩) ويُعيد ⁨true⁩ فقط عندما تكون هذه الاستدعاءة هي التي كتبت الكائن. و⁨put()⁩ هو الاستبدال غير المشروط المُستخدَم حصراً عندما يطلب الـ ⁨manifest⁩ الخيار ⁨overwrite⁩.

JobCompletionEmitterInterface وFilesystemOutboxEmitter

قسم بعنوان «JobCompletionEmitterInterface وFilesystemOutboxEmitter»
public function emit(JobTerminalEvent $event): void;

المُطلِق اختياري في المعالج. ولا تُطلَق الأحداث إلا بعد إنهاء العنصر بصورة دائمة. و⁨FilesystemOutboxEmitter⁩ هو التنفيذ الدائم المشحون:

public function __construct(string $directory, ?AtomicFileWriter $writer = null)

يرمي أو يفشل بـ:InvalidArgumentException⁩ عندما لا يكون الدليل موجوداً؛ ويرمي ⁨emit()⁩ الاستثناء ⁨RuntimeException⁩ إذا تعذّر ترميز حدث بصيغة ⁨JSON⁩. و⁨hasEvent(string $eventId): bool⁩ يفحص صندوق الصادر؛ و⁨count(): int⁩ يُبلّغ عن الأحداث غير المُسلَّمة.

public function __construct(
public string $eventId,
public string $runId,
public int $sourceOffset,
public string $jobId,
public string $idempotencyKeyValue,
public JobTerminalStatus $status,
public ?CommitReceipt $receipt,
public ?string $errorCode,
public ?string $errorMessage,
public int $attempts,
public DateTimeImmutable $occurredAt,
) {}

eventId⁩ حتمي — ⁨runId:sourceOffset:idempotencyKey:status⁩ — وهو ما يجعل إزالة التكرار في صندوق الصادر ممكنة. و⁨toArray()⁩ يُسلسِل الحدث للنقل؛ وهو لا يحمل أي بايتات ⁨PDF⁩. و⁨JobTerminalStatus⁩ تعداد نصّي: ⁨Committed⁩ (⁨committed⁩)، و⁨DeadLettered⁩ (⁨dead_lettered⁩)، و⁨Skipped⁩ (⁨skipped⁩).

عدّادات غير قابلة للتغيير يُعيدها ⁨process()⁩: ⁨runId⁩، و⁨sourceRead⁩، و⁨fastForwardedByCheckpoint⁩، و⁨skippedByIdempotency⁩، و⁨windows⁩، و⁨renderBatchCalls⁩، و⁨renderRetries⁩، و⁨commitReceipts⁩، و⁨deadLettered⁩، و⁨checkpointSaves⁩، و⁨finalCommittedOffset⁩ (العلامة المائية العليا النهائية).

تثبيت مرة-واحدة-تماماً في تخزين الكائنات بمعزل. يحلّ ⁨NullObjectStorageClient⁩ العامل في الذاكرة محل مُهايئ ⁨S3/GCS⁩ لديك؛ والدلالات التي تلاحظها هي ذاتها التي يجب أن يحفظها مُهايئ حيّ.

stream-object-commit-quickstart.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Stream\Storage\NullObjectStorageClient;
use NextPDF\Enterprise\Stream\Storage\ObjectStorageCommitter;
use NextPDF\Manifest\OutputObjectKey;
use NextPDF\Pro\Stream\Exception\OutputCommitConflictException;
use Symfony\Component\Clock\NativeClock;
$committer = new ObjectStorageCommitter(
client: new NullObjectStorageClient(), // swap in your S3/GCS adapter
scheme: 's3',
clock: new NativeClock(),
);
$target = new OutputObjectKey(scheme: 's3', container: 'invoices', key: '2026/07/inv-1001.pdf');
$bytes = '%PDF-1.7 example-rendered-bytes';
$sha = hash('sha256', $bytes);
$first = $committer->commit('inv-1001', $target, $bytes, $sha);
$replay = $committer->commit('inv-1001', $target, $bytes, $sha); // crash-resume replay
printf("first : reuse=%s, %d bytes\n", var_export($first->idempotentReuse, true), $first->bytesWritten);
printf("replay: reuse=%s\n", var_export($replay->idempotentReuse, true));
try {
$divergent = '%PDF-1.7 different-bytes';
$committer->commit('inv-1001', $target, $divergent, hash('sha256', $divergent));
} catch (OutputCommitConflictException $conflict) {
echo 'conflict: ' . $conflict->specCode() . "\n"; // no silent clobber
}

المُخرَج المتوقّع:

first : reuse=false, 31 bytes
replay: reuse=true
conflict: SPEC-COMMIT-409

تشغيل كامل آمن ضد الأعطال: مخازن ⁨Pro⁩ دائمة، ومُثبِّت تخزين الكائنات، وصندوق صادر دائم، وعلامة مُحلّاة من الترخيص. وإعادة تشغيل ⁨runId⁩ نفسه بعد عطل تتخطّى سريعاً وتتقارب.

stream-run-production.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Licensing\EntitlementEvaluator;
use NextPDF\Enterprise\Stream\DocumentJobStreamProcessor;
use NextPDF\Enterprise\Stream\Exception\StreamProcessorException;
use NextPDF\Enterprise\Stream\FilesystemOutboxEmitter;
use NextPDF\Enterprise\Stream\Storage\ObjectStorageCommitter;
use NextPDF\Enterprise\Stream\StreamProcessorConfig;
use NextPDF\Manifest\Render\SingleDocumentRenderer;
use NextPDF\Manifest\RenderManifest;
use NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore;
use NextPDF\Pro\Stream\Dedup\FilesystemIdempotencyStore;
use NextPDF\Pro\Stream\Engine\InProcessRenderEngine;
use NextPDF\Pro\Stream\Retry\FilesystemDeadLetterStore;
use NextPDF\Pro\Stream\Retry\RetryPolicy;
use NextPDF\Pro\Stream\State\InMemoryKeyedStateStore;
use Symfony\Component\Clock\NativeClock;
// Production requires a host-supplied adapter whose putIfAbsent() is a TRUE
// atomic conditional create (S3 If-None-Match: *, GCS ifGenerationMatch: 0)
// and whose shaOf() reads durable object state. NullObjectStorageClient is
// for the quick start only - it keeps nothing across processes.
$s3Client = new \Aws\S3\S3Client(['region' => 'eu-central-1', 'version' => 'latest']);
$objectClient = new \Acme\Storage\S3ObjectStorageClient($s3Client); // implements ObjectStorageClientInterface
$stateDir = '/var/lib/nextpdf/stream';
foreach (['checkpoints', 'idempotency', 'dead-letters', 'outbox'] as $sub) {
if (!is_dir($stateDir . '/' . $sub)) {
mkdir($stateDir . '/' . $sub, 0770, true);
}
}
// One manifest per JSONL line; the generator never materialises the batch.
$manifests = (static function (string $path): Generator {
$handle = fopen($path, 'rb');
if ($handle === false) {
throw new RuntimeException('Cannot open job stream: ' . $path);
}
try {
while (($line = fgets($handle)) !== false) {
if (trim($line) !== '') {
yield RenderManifest::fromJson(trim($line));
}
}
} finally {
fclose($handle);
}
})('/var/spool/nextpdf/jobs.jsonl');
$license = null; // your licensing bootstrap yields a LicenseKey; null = evaluation branding
$processor = DocumentJobStreamProcessor::withBrandingFromLicense(
engine: new InProcessRenderEngine(SingleDocumentRenderer::standalone()),
// For a live bucket, implement ObjectStorageClientInterface over your S3/GCS SDK.
committer: new ObjectStorageCommitter($objectClient, 's3', new NativeClock()),
idempotency: new FilesystemIdempotencyStore($stateDir . '/idempotency'),
checkpoints: new FilesystemCheckpointStore($stateDir . '/checkpoints'),
state: new InMemoryKeyedStateStore(), // recomputable; durability not required here
deadLetters: new FilesystemDeadLetterStore($stateDir . '/dead-letters'),
retryPolicy: new RetryPolicy(maxAttempts: 3, baseDelayMs: 200, maxDelayMs: 5_000),
clock: new NativeClock(),
entitlementEvaluator: new EntitlementEvaluator(),
license: $license,
emitter: new FilesystemOutboxEmitter($stateDir . '/outbox'),
);
$config = new StreamProcessorConfig(
runId: 'nightly-invoices-2026-07-03',
windowSize: 32,
checkpointIntervalJobs: 100,
crashSafe: true,
);
try {
$summary = $processor->process($manifests, $config);
} catch (StreamProcessorException $e) {
// Ambiguous commit or a non-durable collaborator: the finalized prefix is
// checkpointed. Re-run the SAME runId; the committer converges by digest.
fwrite(STDERR, 'Run aborted for safe resume: ' . $e->getMessage() . PHP_EOL);
exit(1);
}
printf(
"run %s: read=%d committed=%d dedup-skipped=%d dead-lettered=%d checkpoints=%d final-offset=%d\n",
$summary->runId,
$summary->sourceRead,
$summary->commitReceipts,
$summary->skippedByIdempotency,
$summary->deadLettered,
$summary->checkpointSaves,
$summary->finalCommittedOffset,
);

مثال على المُخرَج (تعتمد العدّادات على تدفق مهامك):

run nightly-invoices-2026-07-03: read=1200 committed=1187 dedup-skipped=13 dead-lettered=0 checkpoints=12 final-offset=1200
  • كاتب-واحد لكل ⁨runId⁩ مسؤوليتك. مخزن نقاط التفتيش لا يملك حجزاً ولا مقارنة-وتبديلاً. وجود كاتبَين متزامنَين على ⁨runId⁩ واحد خارج العقد؛ افرض الحصرية في مُجدولك.
  • crashSafe: true⁩ يفشل سريعاً مع المتعاونين غير الدائمين. يجب أن تُنفّذ مخازن المُثبِّت ونقطة التفتيش والتكافؤ والرسائل الميتة جميعها واسِم ⁨DurableCapability⁩، وإلا رمى ⁨process()⁩ الاستثناء ⁨StreamProcessorException⁩ مُسمّياً المُخالفين. ومخزن الحالة المُفهرَسة مُعفى عن قصد: تُعاد حَوسبة الحالة المُفهرَسة المفقودة انطلاقاً من نقطة التفتيش.
  • windowSize⁩ يجب أن يلائم المحرك. نافذة أكبر من ⁨maxBatchSize()⁩ ترمي ⁨InvalidArgumentException⁩ قبل بدء أي عمل.
  • التثبيت الغامض يُجهض؛ والتعارض لا.SPEC-COMMIT-409⁩ تعارض نهائي حتمي: يُحال العنصر إلى الرسائل الميتة ويستمر التشغيل. أما أي إخفاق تثبيت آخر فغامض: تُسجَّل البادئة المُنهاة في نقطة تفتيش ويرمي التشغيل من أجل استئناف آمن.
  • إخفاقات العرض لا تُجهض التشغيل أبداً. نتيجة ⁨Failed⁩ لعنصر، أو استنفاد ميزانية إعادة المحاولة، أو بايتات تقييم يتعذّر وسمها — كلها تُحيل ذلك العنصر إلى الرسائل الميتة وتستمر.
  • قيم ⁨jobId⁩ المكرّرة آمنة؛ والعمل المكرّر مُفهرَس عبر ⁨idempotencyKey⁩. تُربَط النتائج بالعناصر عبر إزاحة مصدر فريدة، لا عبر ⁨jobId⁩ أبداً. ويُتعرَّف على مفتاح التكافؤ المكرّر حتى ضمن فترة الحاجز نفسها، قبل إعادة العرض.
  • دوام المُطلِق يحسم دلالات الأحداث. المُطلِق ذو النداء الارتجاعي البسيط مُراقِب فقط ومرة-على-الأكثر عبر العطل. و⁨FilesystemOutboxEmitter⁩ يجعل صندوق الصادر دائماً ومُفهرَساً لإزالة التكرار؛ فيصير تسليم المُرحِّل مرة-على-الأقل، وتتطلّب المرة-الواحدة-تماماً لاحقاً إزالةَ تكرار من المستهلك عبر ⁨eventId⁩. ويجب أن يكون دليله (كحال كل مخزن نظام ملفات) موجوداً مسبقاً، وإلا رمى المُنشِئ ⁨InvalidArgumentException⁩.
  • الأحداث المُتخطّاة مُعطَّلة افتراضياً. اضبط ⁨emitSkippedCompletions: true⁩ لإطلاق حدث نهائي ⁨Skipped⁩ أيضاً للعناصر التي قُصِرت دائرتها بإزالة التكرار.
  • مفاتيح الإخراج تفشل بالإغلاق. يعيد ⁨commit()⁩ التأكيد أن مفتاح الهدف آمن نسبةً إلى الحاوية: لا اجتياز بـ ⁨..⁩، ولا هروب مطلق، ولا بايت صِفري، ولا مخطّط مُغلِّف تدفق مُضمَّن، ولا نقطتان رأسيتان (وهو ما يُغلق ناقل تدفق البيانات البديل في ⁨NTFS⁩). والمفاتيح غير الآمنة ترمي قبل أي استدعاء تخزين.
  • تُعاد التحقّق من السلامة عند الحدّ. يعيد المُثبِّت حَوسبة ⁨sha-256⁩ على البايتات الفعلية ويرفض أي عدم تطابق بـ ⁨CommitIntegrityException⁩، فلا يمكن أن يستقر تسليم تالف بصمت.
  • الأحداث لا تحمل محتوى المستند. يحمل ⁨JobTerminalEvent⁩ وصفوف صندوق الصادر مُعرّفات وبصمات وطوابع زمنية وسلاسل أخطاء فقط. وقد تُردّد رسائل الأخطاء تشخيصات المحرك؛ نظّفها، ونظّف أي مخطّط ⁨jobId⁩ يُعرّف المستأجر، قبل شحن ملفات صندوق الصادر إلى وجهات طرف ثالث.
  • مُخرَج التقييم لا يُنشر أبداً دون علامة. حين تكون العلامة مطلوبة ويتعذّر تطبيقها، يُحال العنصر إلى الرسائل الميتة بدل تثبيته.
  • المرة-الواحدة-تماماً عبر الكُتّاب المتعددين بقوة مُهايئك لا أكثر. إن لم يكن ⁨putIfAbsent()⁩ كتابة شرطية ذرّية حقيقية، تراجع الضمان إلى دلالات كاتب-واحد. واعتمادات مخزن الكائنات وسياسة الحاوية شأن المضيف؛ والوحدة لا تديرها أبداً.

لا يوجد معيار منشور يُعرّف سلوك هذه الوحدة. فضمانات المرة-الواحدة-تماماً ونقطة التفتيش وصندوق الصادر في هذه الصفحة عقود هندسية لواجهة ⁨NextPDF Enterprise⁩ البرمجية، مذكورة هنا بوصفها سلوكاً يمكن ملاحظته خارجياً — وهي ليست مطابقةً لأي معيار ولا شهادةً مقابله. والاستخدام الداخلي لـ ⁨SHA-256⁩ بوصفه بصمة سلامة هو كذلك سباكة، لا ادّعاء امتثال. وكما هو الحال في كل مكان في ⁨NextPDF⁩: الدعم ليس مطابقة، والمطابقة ليست شهادة. ولا يحمل ⁨NextPDF⁩ أي شهادة ولا يمنح أياً منها؛ وأما ما إذا كان نشرٌ مبنيٌّ على هذه الوحدة يفي بالتزاماتك التنظيمية أو التعاقدية فذلك تقدير يعود إلى مُقيّميك.

الأحداث النهائية ليست جزءاً من معاملة نقطة التفتيش: يقع الإطلاق بعد ⁨checkpoint.save()⁩، فيحفظ صندوق الصادر كل حدث مُطلَق بصورة دائمة لكنه ليس سجلّاً كاملاً عبر الأعطال. وتبقى الكائنات المُثبَّتة مصدر الحقيقة.

  • كل إزاحة مصدر عند ⁨finalCommittedOffset⁩ أو دونها بلغت نتيجة نهائية واحدة تماماً: ⁨Committed⁩، أو ⁨Skipped⁩، أو ⁨DeadLettered⁩.
  • تُنهى العناصر وفق ترتيب إزاحة المصدر؛ وترتيب الحاجز هو التثبيت، ثم حفظ نقطة التفتيش، ثم إفراغ علامة التكافؤ، ثم إطلاق الحدث.
  • إعادة تشغيل تشغيلٍ بـ ⁨runId⁩ نفسه لا تنشر مرتين أبداً: تتخطّى الإزاحات المُسجَّلة في نقاط التفتيش سريعاً، وإعادات التثبيت المتطابقة بايتياً عمليات لا-تأثير لها بعد مقارنة البصمة مع ⁨idempotentReuse: true⁩.
  • لا يُنشأ كائن جديد إلا عبر الإنشاء الشرطي الذرّي؛ والبايتات المتباينة عند مفتاح مشغول دون ⁨overwrite⁩ هي إحالة ⁨SPEC-COMMIT-409⁩ حتمية إلى الرسائل الميتة، لا دهساً أبداً.
  • التثبيت الغامض يسجّل البادئة المُنهاة في نقطة تفتيش ويُجهض بـ ⁨StreamProcessorException⁩؛ ولا تُقدَّم الإزاحة الفاشلة.
  • تشغيل ⁨crashSafe⁩ يرفض متعاوني المُثبِّت أو نقطة التفتيش أو التكافؤ أو الرسائل الميتة غير الدائمين قبل قراءة أي مُدخَل.
  • مُعرّفات الأحداث دالة خالصة من التشغيل والإزاحة ومفتاح التكافؤ والحالة، فيحمل صندوق الصادر الدائم صفاً واحداً على الأكثر لكل حدث.

يعرض ⁨NextPDF Core⁩ مستنداً واحداً في المرة عبر الكاتب وعقد بيان-العرض (render-manifest) — انظر الكاتب. و⁨Core⁩ وحده لا يملك تدفقات مهام دائمة، ولا استئنافاً من نقطة تفتيش، ولا إزالة تكرار بالتكافؤ، ولا تثبيت تخزين كائنات، ولا صندوق صادر لأحداث نهائية. ويضيف ⁨NextPDF Pro⁩ محرك العرض الدائم والمتزامن ومخازن نظام الملفات أحادية المضيف (التدفق في ⁨Pro⁩). أما النصف العابر للمضيفات — هذا المعالج، ومُثبِّت تخزين الكائنات، وصندوق الصادر الدائم — فيتطلّب ⁨NextPDF Enterprise⁩.

توثّق هذه الصفحة السلوك القابل للملاحظة خارجياً وسطح واجهة البرمجة العامة المدعوم فقط. أما مسارات فضاء الأسماء الداخلية، والأصناف المساعدة، وجداول الآليات، وأسماء ملفات كتيّبات التشغيل، وبادئات التذاكر فخارج النطاق.