Manifest, NDJSON, _type/_since; batas JSON.parse di Node
Apa yang Anda terima setelah export selesai, dan kenapa file itu tidak boleh dibaca sekaligus
Sebelumnya, di lesson 0013, polling URL status berakhir dengan 200 OK + manifest.
Lesson ini membedah manifest itu, format file NDJSON yang ditunjuknya, parameter _type dan _since, lalu masalah pertama di sisi Node: JSON.parse satu file besar.
Seperti seluruh modul ini, tanpa lab: potongan kode hanya untuk dibaca dan dijelaskan.
transactionTime, output, error, requiresAccessToken),
menjelaskan NDJSON dan _since untuk export inkremental, dan menyebut tiga alasan JSON.parse satu file besar itu salah:
batas string, event loop terblok, dan format NDJSON.
1. Manifest, NDJSON, _type dan _since
{
"transactionTime": "2026-10-26T01:00:00Z",
"request": "https://fhir.example.org/fhir/Patient/$export?_type=Patient,Observation",
"requiresAccessToken": true,
"output": [
{ "type": "Patient", "url": "https://files.example.org/42/Patient_1.ndjson" },
{ "type": "Observation", "url": "https://files.example.org/42/Observation_1.ndjson", "count": 1000000 },
{ "type": "Observation", "url": "https://files.example.org/42/Observation_2.ndjson", "count": 734120 }
],
"deleted": [],
"error": [
{ "type": "OperationOutcome", "url": "https://files.example.org/42/err_1.ndjson" }
]
}
| Field | Arti (Bulk Data IG v2.0.0) |
|---|---|
transactionTime | wajib; "the server's time when the query is run". Export SHALL memuat semua resource yang cocok sampai dan termasuk instant ini. |
requiresAccessToken | wajib; true berarti unduhan file butuh token yang sama. Boleh false untuk file server dengan skema lain, mis. URL bucket S3. |
output | wajib; satu item {type, url, count?} per file. "Each file SHALL contain resources of only one type, but a server MAY create more than one file for each resource type returned." |
error | wajib (boleh kosong); file NDJSON berisi OperationOutcome. Export yang sebagian gagal tetap 200: "In the case of a partial success, the server SHALL use a 200 status code". |
deleted | opsional; dengan _since, berisi Bundle transaction dengan request.method = DELETE untuk resource yang dihapus sejak waktu itu. |
header Expires | SHOULD; kapan file tidak lagi tersedia. Unduh dan proses sebelum itu. |
Kutipan tabel: [1]
NDJSON (newline delimited JSON): "resources are serialized with no whitespace, and separated by a newline pair (characters 13 and 10)", MIME type
application/fhir+ndjson, dan "each nd-json document contains only resources of a single type". [2]
Padanan JS: format ini sama dengan JSON Lines yang mungkin Anda kenal dari log. Isi Observation_1.ndjson, dua baris pertama:
{"resourceType":"Observation","id":"obs-1","status":"final","code":{"coding":[{"system":"http://loinc.org","code":"8310-5","display":"Body temperature"}]},"subject":{"reference":"Patient/p-1"},"valueQuantity":{"value":36.50,"unit":"Cel","system":"http://unitsofmeasure.org","code":"Cel"}}
{"resourceType":"Observation","id":"obs-2","status":"final","code":{"coding":[{"system":"http://loinc.org","code":"8310-5","display":"Body temperature"}]},"subject":{"reference":"Patient/p-2"},"valueQuantity":{"value":37.20,"unit":"Cel","system":"http://unitsofmeasure.org","code":"Cel"}}
Kode LOINC 8310-5 (Body temperature) dari profil vital signs R4. [3] Perhatikan 36.50; kita kembali ke angka ini di lesson 0016.
Parameter kick-off yang paling sering ditanya: [1]
_type: daftar tipe resource dipisah koma; "The response SHALL be filtered to only include resources of the specified resource types(s)." Tanpa_type, server mengembalikan semua tipe yang didukung dalam batas otorisasi client._since(FHIR instant): hanya resource yang "state has changed after the supplied time (e.g., if Resource.meta.lastUpdated is later than the supplied _since time)". Untuk export inkremental, isi dengantransactionTimedari manifest sebelumnya, seperti pola Group di IG yang menyuruh client "retains the transactionTime value from the response". [1]_outputFormat: defaultapplication/fhir+ndjson; server SHALL mendukung NDJSON._elementsdan_typeFilterberstatus experimental.
Data yang diekspor hanya versi terbaru setiap resource: "The exported data SHALL include only the most recent version of any exported resources". [1]
Latihan 1 — Membaca manifest
Manifest menunjuk file NDJSON yang bisa berukuran gigabyte. Langkah berikutnya membaca file itu di Node, dan cara yang paling jelas justru yang salah.
2. Di Node: kenapa JSON.parse satu file besar itu salah
// JANGAN untuk file hasil $export
const text = await readFile("Observation_1.ndjson", "utf8"); // seluruh file → satu string
const all = JSON.parse(text); // + seluruh objek di heap, sekaligus
| Masalah | Kenapa |
|---|---|
| Batas string | buffer.constants.MAX_STRING_LENGTH: "The largest length allowed for a single string instance", dan "This value may depend on the JS engine". [4] Di Node 24.21.0 yang saya cek nilainya 536.870.888 UTF-16 code unit, jauh di bawah file 3 GB. |
| Memori | Teks mentah dan hasil parse ada di heap bersamaan; jutaan objek kecil ditahan sampai Anda selesai. |
| Event loop terblok | JSON.parse dan JSON.stringify "are O(n) in the length of the input, for large n they can take surprisingly long". Contoh resmi: parse string 50 MB makan 1,3 detik, dan selama itu tidak ada request lain yang dilayani. [5] |
| Format | NDJSON adalah banyak dokumen JSON dipisah newline, bukan satu array; JSON.parse pada seluruh isi file langsung melempar SyntaxError. |
Solusinya selalu sama: jangan pernah memegang seluruh file. Baca potongan demi potongan, parse satu resource setiap kali, lalu buang.
Latihan 2 — Batas JSON.parse
Latihan 3 — Ucapkan dengan suara keras
Pasang timer dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.
"Export Anda selesai dengan 200 OK. Jelaskan isi manifest-nya dan bagaimana Anda memakainya untuk export harian yang inkremental." (60 detik)
Bandingkan dengan jawaban contoh dan checklist-nya
"Body 200 adalah manifest JSON. transactionTime adalah jam server saat query dijalankan; export memuat semua resource yang cocok sampai instant itu.
output berisi satu item {type, url, count?} per file: satu file hanya satu tipe resource, tapi satu tipe bisa dipecah ke beberapa file.
error berisi file NDJSON OperationOutcome; export yang sebagian gagal tetap 200. requiresAccessToken: true berarti unduhan file butuh token yang sama.
Setiap file NDJSON, satu baris satu resource, dengan MIME type application/fhir+ndjson, dan harus diunduh sebelum header Expires.
Untuk export harian, saya simpan transactionTime dan memakainya sebagai _since berikutnya, bukan jam client; _type membatasi tipe resource,
dan dengan _since array deleted bisa berisi resource yang dihapus."
Checklist: apakah Anda menyebut…
transactionTime: jam server saat query dijalankan, export memuat sampai instant itu?output:{type, url, count?}per file; satu file satu tipe, satu tipe bisa beberapa file?error: file NDJSON berisi OperationOutcome; sukses sebagian tetap 200?requiresAccessToken: true: unduhan file memakai token yang sama?- NDJSON satu baris satu resource (
application/fhir+ndjson), diunduh sebelumExpires? _since=transactionTimesebelumnya (bukan jam client),_typemembatasi tipe,deleteduntuk yang dihapus?
Lulus jika minimal 4 dari 6.
Berikutnya, lesson 0015: cara yang benar, yaitu streaming baris per baris, batching ke database, backpressure, dan pipeline.
Istilah: GLOSSARY.md (bagian "Bulk Data dan JSON skala besar") · Indeks: semua lesson FHIR
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "apa beda Patient/$export dan Group/[id]/$export untuk use case analitik?", "bagaimana menulis hasil export ke S3 dengan backpressure yang benar?", atau "kapan lebih baik memakai worker thread daripada streaming?"
Sumber: [1] Bulk Data Access IG v2.0.0 (STU 2): Export · [2] FHIR R4: ND-JSON Representation of Resources · [3] FHIR R4: Vital Signs profile · [4] Node.js: buffer.constants.MAX_STRING_LENGTH · [5] Node.js Learn: Don't Block the Event Loop