콘텐츠로 이동

데이터 소스와 리소스 불러오기

Core는 데이터를 읽고, 리소스 경로를 해석하며, 불러오기와 정리를 관리합니다. 데이터 형식은 그대로 두고, 표시는 Vega, Cassiopeia, Home Spot이 각각 맡습니다. 여기서는 실제 JSON 파일과 이미지로 시작하고, 재생에는 Vega 가이드를 쓰세요. 호스팅된 https://haneoka.org/embed/core.js는 npm 설치 없이 브라우저에서 바로 동작합니다.

완전한 promenade.json 열기. 제목, 배경 경로, 텍스트 한 줄이 들어 있으며, garden.svg가 그 원본 이미지입니다. 아래 애플리케이션에서 HTTP JSON을 고르고 불러오면 데이터가 보입니다. Resolve resource URL은 이미지 주소를, Read resource bytes는 실제 내용을 읽습니다. 이 lines 문서는 읽기용 데이터이며 재생 가능한 Vega 문서가 아닙니다. 3단계에서 이 형식을 변환합니다.

JSON 매개변수를 편집하고 불러옵니다. 모듈과 요청은 클릭한 뒤에만 시작되며, 결과에는 실제 응답과 전송 기록이 포함됩니다.

언어

예제를 준비하는 중…

예제 소스 보기
import { createEmbedLoader, httpDataSource } from "https://haneoka.org/embed/core.js";
const loader = createEmbedLoader({
source: httpDataSource({ url: "https://docs.haneoka.org/examples/promenade.json" }),
locale: "fr",
});
const data = await loader.load();
const imageUrl = await loader.resourceUrl(data.background);
// After the consumer finishes with this address: await loader.dispose().

HTTP 리소스는 설정한 JSON 디렉터리 기준으로 해석되며, 리디렉션되어도 바뀌지 않습니다. 명시적 assetsBase가 이를 덮어씁니다. resourceUrl은 주소를, resourceBytes는 내용을 얻습니다. 패널 필드로 팩토리와 리소스를 고릅니다. resourceKey와 resourceField는 이 예제에서 리소스를 고르는 필드이며, SDK 전체 옵션은 아래에 나열되어 있습니다.

2. 실제 로컬 JSON과 이미지 고르기

섹션 제목: “2. 실제 로컬 JSON과 이미지 고르기”

promenade.json과 garden.svg를 저장합니다. 완전한 파일 리더를 열고 JSON과 이미지를 고른 뒤 Load files를 누르세요. Une promenade, 원래 정원 이미지, 이미지의 실제 바이트 수가 보여야 합니다. Close reader는 로더와 Blob URL을 해제하기 전에 이미지를 없앱니다. 새 파일을 고르면 다시 불러올 수 있습니다.

이 프로젝트를 남기려면 아래 코드 전체를 UTF-8 index.html로 저장하세요. 로컬 HTTP로 여는 방법은 빠른 시작에서 설명합니다. 폴더에는 index.html, promenade.json, garden.svg를 둡니다. JSON의 background는 리소스 키이며, 맵이 그 문자 그대로의 키를 고른 File에 연결합니다. 올바른 유니코드 경로도 쓸 수 있습니다.

JSON과 필요한 리소스는 따로 고릅니다. 이 예제는 이미지 하나를 연결하지만, 큰 프로젝트에서는 모델 부속 파일을 포함해 선언된 모든 리소스 키를 고른 File에 연결합니다.

3. 이 읽기용 데이터를 스토리로 변환

섹션 제목: “3. 이 읽기용 데이터를 스토리로 변환”

promenade.json은 이 페이지의 데이터 형식입니다. Vega에는 AdvStory commands 배열이 필요합니다. 이 lines 구조라면 데이터를 읽은 뒤 다음 객체를 만듭니다.

const storyDocument = {
localization: { locales: [data.language], defaultLocale: data.language },
commands: [
{ command: 25, background: { url: data.background } },
...data.lines.map(line => ({ command: 2, targetName: line.speaker, text: line.text })),
],
};

storyDocument를 mountStory의 document로 넘기고, 같은 리소스 디렉터리를 assetsBase로 씁니다. 완전한 브라우저 페이지와 불러오기 단계는 Vega에 있습니다. 고른 JSON이 이미 완전한 AdvStory라면 이 lines 변환 없이 바로 읽으세요. 디코더는 실제 데이터를 검증·변환하고, TypeScript 타입은 기대를 설명할 뿐입니다.

예제에서 Haneoka를 고르고, jacketUrl을 해석하기 전에 공개 곡을 불러옵니다. 리더와 카탈로그 어댑터 모두 호스팅된 진입점에서 쓸 수 있습니다.

import { createEmbedLoader, haneokaDataSource } from "https://haneoka.org/embed/core.js";
const loader = createEmbedLoader({
source: haneokaDataSource({ resource: "songs", id: "100070" }),
server: "intl", locale: "en",
});
const song = await loader.load();
const imageUrl = await loader.resourceUrl(song.jacketUrl);
// End the consumer before awaiting loader.dispose().

공개 읽기에는 계정이나 릴리스가 필요 없습니다. server는 레지스트리를, locale은 언어 맥락을 고르며, 카탈로그는 제공된 언어별 필드를 유지합니다. 소스의 리소스 기준은 기본으로 API 출처이며 loader.assetsBase로 덮어쓸 수 있습니다. load를 반복하면 현재 데이터를 다시 읽습니다. 기존 객체에는 memoryDataSource를, 고른 File에는 fileDataSource를 쓰세요. 전체 시그니처와 전송 제약은 아래와 같습니다.


필드타입·필수·기본값효과, 조합, 오류
source필수 DataSource<T>load(context)를 제공해야 한다. 로더가 불러올 때마다 호출한다. 선택 source assetsBase가 리소스 디렉터리를, 선택 dispose가 한 번의 정리를 맡는다. 사용하는 호스트 형식에 맞는 소스를 고른다.
server선택 문자열, 기본 undefined사용자 정의 소스·해석기에 그대로 넘긴다. Haneoka 어댑터는 기본값을 intl로 두고 공개 클라이언트로 레지스트리 슬러그를 검증한다. 언어가 서버를 고르지는 않는다.
locale선택 문자열, 기본 en그대로 넘긴다. fr, pt-BR 같은 작성자 로캘도 유효하다. Haneoka는 지원 언어 협상을 위해 Accept-Language로 보낸다.
assetsBase선택 문자열, 기본 source.assetsBase, 없으면 없음소스 기준을 덮어쓴다. 쿼리·프래그먼트가 없는 절대 HTTP(S) 디렉터리이며 끝에 슬래시를 붙인다. 잘못된 기준은 생성 시 TypeError. 기준 없는 상대 키는 URL 생성에 실패한다.
resolveResource선택 (key:string,context:DataContext)=>string|URL|Blob|Promise<…>, 기본은 키 그대로리소스 키 대응을 완전히 정한다. 상대 결과 URL은 assetsBase를 쓴다. 해석기가 거부하면 그 리소스 작업도 거부되며, 로컬 Map에 없는 항목이 HTTP로 넘어가지 않는다.
fetcher선택 (Request)=>Promise<Response>, 기본 fetchcors Request, 호출자 헤더, 설정된 자격 증명, 신호를 받는다. 문서와 리소스 읽기 모두에 쓴다. 하위 미디어 요소는 자체 전송을 쓴다. 예외를 던지면 작업이 거부된다.
credentials선택 omit/same-origin/include, 기본 omit로더 전송에 적용한다. 자격 증명이 있는 교차 출처 서비스는 임베드 출처와 자격 증명을 허용해야 한다. 잘못된 값은 네이티브 Request가 거부한다.
headers선택 HeadersInit, 기본 없음공유 전송 헤더. 소스의 요청별 헤더가 같은 이름을 덮어쓴다. Haneoka 어댑터는 locale로 Accept-Language를 설정한다. 헤더 검증은 Fetch를 따른다.
maxBytes선택 양의 안전한 정수, 기본 67108864디코딩된 JSON·리소스 바이트 응답 하나당 예산이며, 장면 전체·캐시·URL 해석 합계 예산이 아니다. 0 이하나 안전하지 않은 값은 생성 시 RangeError, 너무 큰 읽기는 RangeError(Haneoka는 response_too_large로 감싼다).
onListenerError선택 (error:unknown)=>void, 기본 무시관찰자 예외를 받는다. 자신의 예외는 격리되며 작업 결과를 바꾸지 않는다.

로더에는 생성 시 신호, 시간 제한, 디코더, 자동 재시도·캐시 옵션이 없습니다. 작업별 신호, 소스 디코더, 전송·소스 정책으로 대응하세요. URL을 해석하기만 해서는 바이트를 내려받지 않으며, 최종 미디어 요소에 maxBytes가 적용되지도 않습니다.

팩토리필수 인수선택 인수·기본값리소스 출처와 검증
memoryDataSource(data)아무 T, 직접 만든 문서 포함없음원래 값·참조를 그대로 반환한다. 기본 에셋 디렉터리나 복제·검증이 없다.
httpDataSource({url,decode?})절대 HTTP(S) 문자열이나 URLdecode(unknown)→T, 기본은 파싱된 JSON팩토리가 프로토콜·URL을 검증한다. 기준은 리디렉션 대상이 아니라 설정한 URL의 디렉터리다. 로더 maxBytes·신호가 적용되며, JSON 파싱·디코딩 실패는 load를 거부한다.
fileDataSource(file,decode?)브라우저 File이나 Blob, 호스트가 만든 Blob 포함같은 디코더, 기본은 파싱된 JSON네트워크 기준이 없다. 로더 예산·진행률로 실제 Blob 바이트를 읽는다. 잘못된 JSON·디코더 실패는 load를 거부한다.
fileResourceResolver(files)ReadonlyMap<string,Blob>없음정규화 없는 유니코드 키 정확 일치 조회. 키가 없으면 Error. URL 호출은 소유한 Blob URL을 만들고, 바이트 호출은 Blob을 바로 읽는다.
haneokaDataSource({resource,id?,view?,apiBase?,decode?})resource 문자열아래 참고현재 공개 카탈로그 어댑터. 계정이나 release 매개변수가 없다.

Haneoka 팩토리 필드: resource는 공개 클라이언트 리소스 구문을 따르는 매니페스트 이름입니다. id?:string은 엔티티를 고르고(생략하면 색인), view?:string은 매니페스트 프로젝션을 고릅니다. apiBase?:string의 기본값은 https://haneoka.org/api/v1/이며 쿼리·프래그먼트 없는 절대 HTTP(S) 디렉터리여야 합니다. decode?는 색인·엔티티를 검증·변환합니다(기본은 JSON 값). 구문 오류나 데이터가 없는 오류는 load할 때 클라이언트 규칙을 따릅니다. 소스 에셋 기준은 API 출처 루트이며, 로더 assetsBase로 명시적으로 덮어쓸 수 있습니다. server와 locale은 이 팩토리가 아니라 로더 맥락에 속합니다.

사용자 정의 소스와 해석기 맥락

섹션 제목: “사용자 정의 소스와 해석기 맥락”

DataSource<T>는 load(context): T|Promise<T>, 선택 assetsBase?:string, 선택 dispose():void|Promise<void>를 선언합니다. 로더는 해제할 때 정리를 한 번 호출하고 기다립니다. 정리가 거부되면 공유 해제 Promise도 거부됩니다. 정리가 다른 로더가 쓰는 리소스를 해제한다면 새로 전용 소스를 쓰세요.

DataContext에는 다음 공개 필드 6개가 있습니다.

필드타입의미
server선택 문자열설정한 소스 레지스트리, 그대로.
locale문자열설정했거나 기본인 작성자 언어.
signalAbortSignal그 작업의 취소 신호. 직접 처리할 때 쓴다.
fetcher(Request)→Promise<Response>로더에 설정한 전송.
maxBytesnumber유효한 디코딩 바이트 예산. 사용자 정의 파싱도 지켜야 한다.
progress({loaded:number,total?:number})→void작업이 실제로 읽은 바이트를 보고한다. 모르면 total을 뺀다.

progress 인수는 자체 loaded와 선택 total 필드를 가집니다. 해석기는 문자열, URL, Blob을 동기·비동기로 반환합니다. 절대 HTTP/HTTPS/Blob/data URL은 출처를 유지하고, 상대 문자열은 유효한 assetsBase를 쓰며 /는 그 출처 루트에서 시작합니다. 다른 프로토콜은 TypeError를 던집니다. 로더가 브라우저나 SSR 페이지 기준을 멋대로 만들지 않습니다.

const source = {
assetsBase: "https://author.example/project/",
async load(context) {
context.signal.throwIfAborted();
const response = await context.fetcher(new Request("https://author.example/project/story.json", { signal: context.signal }));
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json(); // use your format-specific bounded decoder here
},
dispose() { /* release resources owned by this source */ },
};
const loader = createEmbedLoader({ source, locale: "pt-BR", resolveResource: key => key });
멤버인수·기본값과 결과수명 동작
locale읽기 전용 문자열유효 로캘.
server읽기 전용 문자열 또는 undefined어댑터가 대체값을 적용하기 전의 명시적 서버.
disposed읽기 전용 불리언해제가 시작되면 바로 true가 된다.
load(options={})선택 {signal?:AbortSignal}, Promise<T>새 독립 소스 작업, 키는 $data. 반복하면 소스를 다시 호출한다.
resourceUrl(key,options={})필수 문자열과 선택 신호, Promise<string>키를 해석하되 바이트는 가져오지 않는다. 소유한 Blob마다 URL 하나를 해제 전까지 캐시한다.
resourceBytes(key,options={})필수 문자열과 선택 신호, Promise<Uint8Array>키를 해석하고 상태·예산·진행률과 함께 바이트를 읽는다.
subscribe(listener)필수 (event:EmbedEvent)=>void, 구독 해제 함수를 반환이후 이벤트를 받는다. 상태를 다시 보내지 않는다. 이미 해제되었으면 예외를 던진다.
cancel()인수 없음, void현재 모든 작업을 중단한다. 로더는 다시 쓸 수 있다.
dispose()인수 없음, 공유 Promise<void>즉시 작업을 막고, 진행 중 호출을 중단하고, 소유 URL을 폐기하고, disposed를 알리고, 관찰자를 지우고, 소스 정리를 한 번 호출한다. 멱등. 해제된 로더는 다시 쓸 수 없다.

호출별 신호로 취소하면 사용자 정의 소스가 신호를 무시하더라도 바로 거부됩니다. 로더는 늦게 도착한 ready·progress를 억제합니다. 그래도 바탕 작업을 멈추려면 소스 처리가 context.signal을 따라야 합니다. cancel()은 이후 호출을 막지 않고, dispose()는 로더를 끝냅니다. URL을 폐기하기 전에 사용하는 쪽을 해제하세요. 직접 만든 URL은 직접 관리해야 합니다.

이벤트 종류필드시점
start / readyoperation:number, key:string작업 시작·성공 종료.
progressoperation, key, loaded:number, total?:number실제로 읽은 바이트. 메모리 데이터나 URL만 해석할 때는 자동 진행률이 없다. 압축되었거나 Content-Length를 모르면 total을 뺀다.
error / cancelledoperation, key, error:unknown실패·취소.
disposedtype만로더 해제. 해제 뒤의 취소 실패는 억제된다.

ID는 로더마다 따로 늘어나며, key는 $data나 리소스 키 그대로입니다. 리스너 오류로 작업이 거부되지는 않습니다. 동시 작업은 서로 독립이며, 데이터 단위 중복 제거나 재시도가 없습니다. 작은 데모는 표시하는 기록을 최대 32개로 유지하지만 SDK 이벤트 흐름은 그대로입니다.

EmbedHttpError(status,url)은 Error를 확장한 것으로, 성공하지 않은 HTTP·리소스 읽기에 name, message, status, url을 가집니다. 잘못된 URL·기준·해석된 프로토콜은 TypeError, 바이트 예산 초과는 RangeError, 잘못된 JSON은 SyntaxError를 던지며, 직접 만든 디코더·소스·해석기 오류는 그대로 남습니다. Haneoka API 실패는 ApiClientError를 유지하며, 응답 예산 초과는 코드 response_too_large, HTTP 상태, 기본 재시도 불가로 보고됩니다. 중단은 신호의 이유(보통 AbortError나 TimeoutError)로 거부됩니다. 해제 뒤의 호출은 Error로 거부되고, 소스 정리 실패는 dispose를 거부합니다.

createHaneokaBranding(document,options={})는 호스팅된 core 창구와 패키지 /branding 진입점에서 내보냅니다. 필수 document는 호스트 DOM Document이며, corner?:"top-left"|"top-right"|"bottom-left"|"bottom-right"의 기본값은 top-left입니다. HTMLAnchorElement를 반환하며, 이미지 요청을 하지 않고 스스로 붙지도 않습니다. 위치가 지정된 전체 크기 호스트에 반투명 오버레이로 붙이고, 48px 터치 영역을 유지하며, 정리할 때 제거하세요. href는 https://haneoka.org/로 고정이고 target은 _blank, noopener가 붙습니다. 원래 인라인 SVG, 48px 터치 영역, 포커스가 제공됩니다. 표시용 임베드 호스트는 이 도우미를 자동으로 관리합니다. 데이터 전용 core를 가져와도 DOM은 만들어지지 않습니다.