Download tools/build_api_json.mjs from SharpMeow/unroll: direct link, hf CLI and curl.
- Browser
- Download file 6.79 kB
-
https://huggingface.co/spaces/SharpMeow/unroll/resolve/main/tools/build_api_json.mjs
- Command line
-
hf download hf://spaces/SharpMeow/unroll/tools/build_api_json.mjs
-
curl -L -o build_api_json.mjs https://huggingface.co/spaces/SharpMeow/unroll/resolve/main/tools/build_api_json.mjs
6.79 kB
| // Writes api.json from lib/apispec.js, lib/manifest.js and version.json. Run after changing any of them: | |
| // node tools/build_api_json.mjs (tests/api.mjs checks that api.json is up to date) | |
| import { readFileSync, writeFileSync } from 'node:fs'; | |
| import { API_VERSION, METHODS, EVENTS, URL_PARAMS, KEYS } from '../lib/apispec.js'; | |
| import { CT_SPECS } from '../lib/ctapi.js'; | |
| import { SCHEMA, LOCAL_LIMITS, REGISTRY_LIMITS, MESH_EXT, IMAGE_EXT, ROLES, FORMATS } from '../lib/manifest.js'; | |
| const ver = JSON.parse(readFileSync(new URL('../version.json', import.meta.url), 'utf8')); | |
| const jsType = (t) => t.split('|').map((x) => ({ string: 'string', number: 'number', boolean: 'boolean', object: 'object', array: 'array', null: 'null', function: 'function' }[x] || x)); | |
| const methods = {}; | |
| for (const [name, m] of [...Object.entries(METHODS), ...Object.entries(CT_SPECS).map(([k, v]) => [`ct.${k}`, { group: 'ct', ...v, extension: true }])]) { | |
| methods[name] = { | |
| group: m.group, summary: m.summary, call: `window.unroll.${name}(${m.params.map((p) => p.name + (p.required === false ? '?' : '')).join(', ')})`, | |
| params: m.params.map((p) => ({ name: p.name, type: jsType(p.type), required: p.required !== false, ...(p.desc ? { description: p.desc } : {}), ...(p.enum ? { enum: p.enum } : {}), ...(p.min !== undefined ? { minimum: p.min } : {}), ...(p.max !== undefined ? { maximum: p.max } : {}) })), | |
| returns: 'Promise<{ ok: true, ... } | { ok: false, error: string, code: string }>', | |
| }; | |
| } | |
| const path = { type: 'string', description: 'Relative path inside the package: no URL, no leading slash, no ".."' }; | |
| const layer = { | |
| type: 'object', required: ['id', 'role', 'label'], | |
| properties: { | |
| id: { type: 'string' }, role: { enum: ROLES }, label: { type: 'string' }, shortLabel: { type: 'string', description: 'Label where space is tight (layer chip, compare swipe)' }, | |
| resolutionUM: { type: 'number', exclusiveMinimum: 0, description: 'Resolution in micrometers' }, | |
| windings: { description: '"all", a list of ids, or { range: [first, last] }' }, | |
| texture: { ...path, description: 'Per-winding image path pattern containing {id}' }, | |
| files: { type: 'object', additionalProperties: path, description: 'Winding id to image path, overriding texture' }, | |
| atlas: path, atlasFallback: path, hi: { ...path, description: 'Close-up image pattern containing {id}' }, stats: path, | |
| source: { type: 'string', description: 'Where the layer comes from' }, aboutHtml: { type: 'string', description: 'Registry scrolls only' }, | |
| }, | |
| }; | |
| const manifest = { | |
| $schema: 'https://json-schema.org/draft/2020-12/schema', title: 'Unroll scroll manifest', type: 'object', | |
| required: ['schema', 'id', 'name', 'voxelMM', 'geometry', 'windings', 'layers'], | |
| properties: { | |
| schema: { const: SCHEMA }, id: { type: 'string', pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$' }, name: { type: 'string', maxLength: 120 }, | |
| voxelMM: { type: 'number', exclusiveMinimum: 0, exclusiveMaximum: 10 }, base: { type: 'string', description: 'Relative folder that paths start from' }, | |
| geometry: { type: 'object', required: ['format'], properties: { format: { enum: FORMATS, description: 'grid-int16 is for registry scrolls only' }, units: { enum: ['voxels', 'mm'] }, axis: { enum: ['z', 'y'] }, index: path, bin: path } }, | |
| windings: { description: 'A list of { id, mesh } or { range: [first, last], mesh: pattern with {id}, outermost }', oneOf: [{ type: 'array', items: { type: 'object', required: ['id'], properties: { id: { type: 'string' }, mesh: { ...path, description: `.${MESH_EXT.join(', .')}` } } } }, { type: 'object', required: ['range'] }] }, | |
| outermost: { enum: ['first', 'last'] }, | |
| layers: { type: 'array', minItems: 1, items: layer, description: 'One layer per role; at least one ct or ink layer' }, | |
| description: { type: 'string' }, | |
| credits: { type: 'object', properties: { text: { type: 'string' }, url: { type: 'string', format: 'uri' }, citation: { type: 'string' }, holder: { type: 'string' } } }, | |
| dataTerms: { type: 'object', properties: { license: { type: 'string' }, url: { type: 'string', format: 'uri' }, notes: { type: 'string' } } }, | |
| display: { type: 'object', description: 'Registry scrolls only' }, | |
| }, | |
| 'x-imageExtensions': IMAGE_EXT, 'x-meshExtensions': MESH_EXT, 'x-limits': { openYourOwn: LOCAL_LIMITS, registry: REGISTRY_LIMITS }, | |
| }; | |
| const out = { | |
| name: 'Unroll', app: { version: ver.version, date: ver.date, sha: ver.sha }, apiVersion: API_VERSION, | |
| docs: { llms: '/llms.txt', agents: '/AGENTS.md', addingAScroll: '/docs/ADDING_A_SCROLL.md', changelog: '/CHANGELOG.md', embed: '/docs/EMBED.md', oembed: '/oembed.json' }, | |
| global: 'window.unroll', | |
| conventions: [ | |
| 'Every method returns a Promise of { ok: true, ... } or { ok: false, error, code }. Nothing throws.', | |
| 'Error codes: invalid (bad arguments), not_found, unavailable (feature missing in this build or scroll), timeout, conflict, failed, load_failed.', | |
| 'Screen coordinates are CSS pixels of the viewport; scene coordinates are millimeters.', | |
| 'Add-ons register more groups with unroll.register(group, methods, specs), for example unroll.bench. They appear in unroll.schema() with extension: true.', | |
| 'Every interactive element has data-testid and data-api naming its API equivalent.', | |
| ], | |
| embed: { | |
| url: 'https://sharpmeow-unroll.static.hf.space/index.html?embed=1', | |
| params: { embed: '1: compact layout, click to interact', autoplay: '1: play on load', controls: '0: hide the controls' }, | |
| iframeAllow: 'fullscreen; clipboard-write', | |
| }, | |
| postMessage: { | |
| description: 'Host pages drive an embedded viewer with postMessage. Every method above is callable by name; on, off and register are replaced by subscribe and unsubscribe.', | |
| toViewer: { | |
| call: { unroll: 1, id: 'number|string', method: 'string (an API method name)', args: 'array' }, | |
| subscribe: { unroll: 1, id: 'number|string', method: 'subscribe', args: ['eventName'] }, | |
| unsubscribe: { unroll: 1, id: 'number|string', method: 'unsubscribe', args: ['eventName'] }, | |
| }, | |
| fromViewer: { | |
| ready: { unroll: 1, type: 'ready', version: '{ version, date, sha, api }', methods: 'array of method names' }, | |
| result: { unroll: 1, type: 'result', id: 'the id you sent', result: '{ ok: true, ... } | { ok: false, error, code }' }, | |
| event: { unroll: 1, type: 'event', event: 'string', payload: 'object' }, | |
| }, | |
| forbidden: ['open.listLocal', 'open.removeLocal'], | |
| }, | |
| methods, events: EVENTS, urlParams: URL_PARAMS, keys: KEYS, manifestSchema: manifest, | |
| }; | |
| writeFileSync(new URL('../api.json', import.meta.url), JSON.stringify(out, null, 2) + '\n'); | |
| console.log(`api.json: ${Object.keys(methods).length} methods, app ${ver.version}`); | |