Production-grade infrastructure for Adobe ExtendScript.
|
ESON ESB64 ESARR ESSTR ESCHARS ESHTTP ESTIMER ESRAND ESUUID ESENV ESPATH ESFS ESHASH ESLOG |
ESPACK ESMIN ESABI VectorIPC ESTC ESDB COMTool ESsemble |
Also from the same team: ArcFit.dev, deterministic arc warp for Illustrator.
- Why ESUUID?
- Features
- Which artifact should I use?
- Get the Release
- Installation
- Quick Start
- API Reference
- Validation
- Spec Conformance
- Performance
- Security Model
- Compatibility
- Engine quirks that shaped the design
- Development
- Repository layout
- Credits
- License
ExtendScript is an ES3-era runtime with no standardized UUID API equivalent to modern crypto.randomUUID(). ESUUID provides the RFC 9562 UUID surface directly in ExtendScript syntax with an explicit entropy priority: caller-provided bytes/RNG, then ESRAND, then a warned non-cryptographic Math.random() fallback.
The default Adobe-host facade integrates with ESRAND when it is already installed as $.global.ESRAND. If ESRAND is missing and the caller does not provide entropy, ESUUID falls back to Math.random(), reports entropy: "Math.random-fallback", and emits a one-time warning on the first fallback entropy request.
ESUUID also targets the public API shape developers already know from uuidjs/uuid where that mapping is meaningful in ExtendScript. Differential validation currently covers 3,225 parity checks against [email protected].
- RFC 9562 UUID v1, v3, v4, v5, v6, and v7 generation.
- RFC 9562 Appendix A vectors pass 6/6 for v1/v3/v4/v5/v6/v7.
parse,stringify,validate, andversionmatch theuuidjs/uuid-style surface.v1ToV6andv6ToV1conversions are included and differential-tested.- Namespace constants:
DNS,URL,OID, andX500. - Special constants:
NILandMAX. - Explicit byte-array output buffers and offsets for generation and name-based UUIDs.
- Stateful v1/v6 generation and monotonic v7 sequencing through
create(). - ESRAND-backed default entropy in Adobe hosts when
$.global.ESRAND.bytes(count)is available. - Caller-injected
rng,rand, andnowsources for deterministic tests or custom integrations. - Warned
Math.random()fallback when ESRAND and caller entropy are both unavailable. - Same-version global reload preserves the existing facade and stateful v1/v7 generator state.
- Fresh minified and unminified artifacts are both exercised inside real Illustrator.
- 55 unit checks, 3,225 differential checks, 75,000 fuzz invariants, and 54/54 standalone live Illustrator checks pass; the ESPACK composition lane separately proves ESRAND -> ESUUID activation with no sibling preload.
| Artifact | Size | Surface | Use it when |
|---|---|---|---|
dist/ESUUID.min.jsx |
14,595 bytes | Installs $.global.ESUUID |
Default production ExtendScript script include |
dist/ESUUID.jsx |
18,746 bytes | Installs $.global.ESUUID |
Readable/debuggable ExtendScript build |
dist/vendor-esuuid.min.js |
14,595 bytes | Installs $.global.ESUUID |
Vendoring into another generated bundle |
dist/vendor-esuuid.js |
18,746 bytes | Installs $.global.ESUUID |
Readable vendor input |
dist/ESUUID.bundle.min.jsx |
57,606 bytes | ESPACK v2 activates ESRAND -> ESUUID | Dependency-complete production include; no ESRAND preload |
dist/ESUUID.bundle.jsx |
89,890 bytes | Same composed graph, readable | Debugging the dependency-complete distribution |
dist/ESUUID.facade.jsx |
18,833 bytes | Loader-free ESUUID library node | Input to a larger ESPACK v2 composition |
dist/ESUUID.manifest.json |
77,143 bytes | Exact ESRAND + ESUUID graph/provenance | Build/composition input, not a runtime include |
dist/esuuid-core.esm.mjs |
20,459 bytes | ESM exports | Node-side tests, differential validation, or tooling |
The standalone/vendor artifacts and the ESPACK facade are generated from the
same src/jsx-entry.ts entry point. The composed bundle is resolved after
those bytes exist: ESPACK reads the ESRAND v2 manifest, solves the dependency,
then flattens one loader/control plane followed by ESRAND and ESUUID.
Rule of thumb: use ESUUID.bundle.min.jsx when you want the preferred
ESRAND backend without managing sibling load order. Use ESUUID.min.jsx
when the host intentionally owns the entropy backend lifecycle or wants the
documented Math.random fallback.
Production artifacts ship as GitHub release assets. Grab the immutable builds from the Releases page.
How it works, in three steps:
- Open the Releases page.
- Pick the latest stable tag.
- Download the asset that matches your use case:
| You are... | Take this release | And this asset |
|---|---|---|
| Wanting the preferred ESRAND-backed production include with no sibling preload | Latest stable | ESUUID.bundle.min.jsx (readable: ESUUID.bundle.jsx) |
| Managing the entropy backend yourself or intentionally allowing the documented fallback | Latest stable | ESUUID.min.jsx (readable: ESUUID.jsx) |
| Composing ESUUID into a larger ESPACK distribution | Latest stable | ESUUID.facade.jsx + ESUUID.manifest.json |
| Vendoring ESUUID into another generated artifact | Latest stable | vendor-esuuid.min.js (readable: vendor-esuuid.js) |
| Consuming the reference core from Node tooling | Latest stable | esuuid-core.esm.mjs |
| Verifying immutable release provenance | Latest stable | esuuid-v0.2.0.lock.json + SHA256SUMS.txt + the preserved latest-*.json evidence assets |
ESUUID ships generated dist/ artifacts in the repository so a consumer does not need the TypeScript build toolchain just to use the library.
The dependency-complete distribution requires no manual sibling preload:
$.evalFile(File("/path/to/ESUUID.bundle.min.jsx"));
var id = ESUUID.v7();
// ESUUID.capabilities().entropy === "ESRAND"That one file is an ESPACK v2 composition of ESRAND -> ESUUID. The resolver
deduplicates transitive libraries and verifies both activation contracts before
the root is considered active.
When using the standalone artifact instead, load ESRAND first when you want the same preferred default entropy backend:
$.evalFile(File("/path/to/vendor-esrand.js"));
$.evalFile(File("/path/to/ESUUID.min.jsx"));
var id = ESUUID.v7();ESRAND is available at thelabcorner/es-rand.
The package-level ESRAND peer remains marked optional because the standalone
facade intentionally supports a fallback. That host-time fallback is separate
from the shipped ESUUID.bundle*.jsx contract, where ESRAND is a required
composed dependency.
Requirements:
- Node.js 20 or newer.
- Python 3 for the conservative ExtendScript minifier.
- HTTPS access to the pinned public ESTC source archive.
git clone https://github.com/thelabcorner/es-uuid.git
cd es-uuid
npm ci
npm run buildThe build uses the pinned ESTC source archive installed by npm ci by default. Inside the wider toolkit workspace, set ESUUID_USE_WORKSPACE_ESTC=1 to opt into the sibling ../extendscript-toolchain checkout.
ESUUID can load without ESRAND. If you call a random UUID method without
supplying entropy, the Adobe facade falls back to Math.random(), emits a
one-time warning, and continues:
$.evalFile(File("/path/to/ESUUID.min.jsx"));
var id = ESUUID.v4(); // works, warns once that Math.random() is being usedFor deterministic or stronger externally-supplied entropy, inject a source:
$.evalFile(File("/path/to/ESUUID.min.jsx"));
var uuid = ESUUID.create({
rng: function () {
return [
0x91, 0x91, 0x08, 0xf7,
0x52, 0xd1, 0x33, 0x20,
0x5b, 0xac, 0xf8, 0x47,
0xdb, 0x41, 0x48, 0xa8
];
}
});
var id = uuid.v4();// ESRAND should already be installed on $.global.
$.evalFile(File("/path/to/ESUUID.min.jsx"));
var randomId = ESUUID.v4();
var orderedId = ESUUID.v7();
var stableNameId = ESUUID.v5(
"www.example.com",
ESUUID.DNS
);
var bytes = ESUUID.parse(randomId);
var roundTrip = ESUUID.stringify(bytes);
if (!ESUUID.validate(roundTrip)) {
throw new Error("invalid UUID");
}
$.writeln("v4: " + randomId);
$.writeln("v7: " + orderedId);
$.writeln("v5: " + stableNameId);| Name | Value / purpose |
|---|---|
ESUUID.VERSION |
Library version, currently 0.2.0 |
ESUUID.NIL |
00000000-0000-0000-0000-000000000000 |
ESUUID.MAX |
ffffffff-ffff-ffff-ffff-ffffffffffff |
ESUUID.DNS |
RFC namespace UUID for DNS names |
ESUUID.URL |
RFC namespace UUID for URLs |
ESUUID.OID |
RFC namespace UUID for ISO OIDs |
ESUUID.X500 |
RFC namespace UUID for X.500 DNs |
ESUUID.v1(options);
ESUUID.v4(options);
ESUUID.v6(options);
ESUUID.v7(options);Generation methods return a canonical UUID string by default. Pass a byte-array output buffer and optional offset to write 16 bytes into an existing array:
var out = new Array(32);
ESUUID.v4({ random: random16 }, out, 8);Relevant options:
| Option | Methods | Contract |
|---|---|---|
random |
v1/v4/v6/v7 | Byte array with at least 16 bytes; the first 16 are consumed |
rng |
v1/v4/v6/v7 | Function returning at least 16 byte values |
msecs |
v1/v6/v7 | Explicit millisecond timestamp |
nsecs |
v1/v6 | 100 ns sub-millisecond counter |
clockseq |
v1/v6 | Explicit 14-bit clock sequence |
node |
v1/v6 | Explicit 6-byte node identifier |
seq |
v7 | Explicit unsigned 32-bit sequence |
ESUUID.v3(value, namespace);
ESUUID.v5(value, namespace);value may be a string or byte-like object. namespace may be a UUID string or exactly 16 bytes.
The standard namespace constants are also attached to ESUUID.v3 and ESUUID.v5:
var a = ESUUID.v3("www.example.com", ESUUID.v3.DNS);
var b = ESUUID.v5("www.example.com", ESUUID.v5.DNS);var bytes = ESUUID.parse("919108f7-52d1-4320-9bac-f847db4148a8");
var text = ESUUID.stringify(bytes);
ESUUID.validate(text); // true
ESUUID.version(text); // 4stringify(bytes, offset) reads exactly 16 bytes beginning at offset.
var v6 = ESUUID.v1ToV6(v1);
var v1Again = ESUUID.v6ToV1(v6);The conversion helpers reject valid UUIDs of the wrong source version instead of silently reinterpreting them.
var ids = ESUUID.create({
now: function () {
return new Date().getTime();
}
});
var a = ids.v7();
var b = ids.v7();create() returns a stateful generator exposing v1, v4, v6, and v7. In the Adobe-host facade, a custom now function still inherits the installed ESRAND backend unless the caller explicitly supplies rng or rand.
var caps = ESUUID.capabilities();With ESRAND installed:
standard: RFC 9562
engine: ExtendScript ES3
entropy: ESRAND
cryptographic: false
Without ESRAND, entropy reports Math.random-fallback. That state is
deliberately non-cryptographic and the first actual fallback entropy request
emits a warning.
Current 0.2.0 validation on the public-reproducible build:
| Check | Command | Result |
|---|---|---|
| TypeScript | npm run typecheck |
pass |
| ExtendScript build + ES3 grammar checks | npm run build |
4/4 ExtendScript artifacts pass ESTC acorn-ecma3 checks |
| Unit suite | npm test |
55/55 checks |
| Differential parity | npm run differential |
3,225/3,225 checks vs. [email protected] |
| Seeded invariants | npm run fuzz |
75,000/75,000 checks |
| Live Illustrator | npm run live-verify -- --pipe <runtime> |
54/54 checks on Illustrator 30.6.0 / ExtendScript 4.5.6 |
| Live host benchmark | npm run live-benchmark -- --pipe <runtime> --rounds 3 |
9/9 benchmark lanes completed, 0 rejected samples |
The differential oracle is uuidjs/uuid, version 14.0.2. The live check runs the actual generated ESUUID.jsx and ESUUID.min.jsx artifacts through COM Tool V2 script.runFile, and dogfoods the real sibling ESRAND distribution.
Machine-readable live evidence is committed under evidence/.
ESUUID targets RFC 9562, Universally Unique IDentifiers (UUIDs).
| RFC 9562 Appendix A vector | Result |
|---|---|
| UUIDv1 | pass |
| UUIDv3 | pass |
| UUIDv4 | pass |
| UUIDv5 | pass |
| UUIDv6 | pass |
| UUIDv7 | pass |
The same unit suite also covers NIL/MAX handling, RFC variant validation, UUIDv8 recognition, v1/v6 conversion, output-buffer boundaries, sequence limits, and monotonic state behavior.
Name-based UUIDs use an ES3-compatible MD5 implementation for v3 and SHA-1 implementation for v5 as required by the UUID versions. No uuidjs runtime code is vendored.
Measured live on Adobe Illustrator 30.6.0 / ExtendScript 4.5.6 using the ESTIMER vendor engine lane through COM Tool V2.
Protocol: one benchmark lane per script.runFile; ESTIMER prime; 5 warmups; 9 measured samples per lane; 3 rounds; batch-normalized per-operation values. The table reports the median across the three round summaries, with the observed minimum and p95 envelope recorded by the benchmark harness.
| Lane | Median us/op | Min us/op | p95 us/op | Median ops/s |
|---|---|---|---|---|
| v4, ESRAND entropy | 47.012 | 44.760 | 50.284 | 21,271 |
| v4, Math.random fallback | 51.336 | 50.836 | 62.608 | 19,480 |
| v4, explicit bytes | 37.354 | 35.704 | 45.692 | 26,771 |
| v7, ESRAND entropy | 53.700 | 51.688 | 66.676 | 18,622 |
| v7, Math.random fallback | 61.684 | 59.100 | 64.576 | 16,212 |
| v7, explicit bytes | 45.654 | 43.342 | 51.730 | 21,904 |
| parse | 135.185 | 123.230 | 149.374 | 7,397 |
| stringify | 95.471 | 88.325 | 97.147 | 10,474 |
| v5 | 1,016.220 | 915.400 | 1,190.840 | 984 |
The Math.random lanes measure steady-state fallback generation after the
one-time warning has already been emitted during warmup. These are host-engine
measurements, not Node throughput estimates. Re-run npm run live-benchmark
on the target Adobe host before using them as a capacity estimate for a
different machine or application.
A separate Node microbenchmark exists under tests/benchmark.mjs for development regression checks.
ESUUID is a pure JavaScript/ExtendScript UUID implementation. It does not open sockets, write files, load native code, or evaluate caller-provided source.
ESUUID does not claim cryptographic entropy. ESUUID.capabilities().cryptographic is deliberately false. The default Adobe-host random source is ESRAND, which is designed for deterministic random infrastructure rather than cryptographic key generation.
Consequences:
- Do not use ESUUID-generated UUIDs as passwords, bearer tokens, API secrets, session secrets, or cryptographic nonces.
- UUIDv3 uses MD5 and UUIDv5 uses SHA-1 because those hash functions are part of the specified UUID version algorithms. Their presence is not a recommendation to use MD5/SHA-1 for general-purpose security.
- If ESRAND and caller entropy are both missing, ESUUID falls back to
Math.random(), emits a one-time warning, and reportsentropy: "Math.random-fallback". - The
Math.random()fallback is non-cryptographic and may be predictable; inject a CSPRNG-backed source for security-sensitive identifiers. - Caller-supplied entropy is trusted as supplied. ESUUID validates byte shape and bounds, not randomness quality.
For ordinary identifiers, deterministic fixtures, namespace UUIDs, and time-ordered UUIDs inside ExtendScript automation, the entropy contract is explicit and inspectable.
| Target | Status |
|---|---|
| Adobe Illustrator 30.6.0 | 54/54 live checks pass |
| ExtendScript 4.5.6 | live verified |
| ExtendScript ES3 grammar | 4/4 generated artifacts pass ESTC acorn-ecma3 checks |
| Other Adobe ExtendScript hosts | ES3-compatible build; not yet separately live-certified |
| Node.js | Node 20+ for build/test tooling and ESM differential harness |
| Windows | live COM Tool V2 verification performed on Windows |
| macOS | generated ES3 code is host-neutral; live COM verification is Windows-only |
The runtime library itself does not depend on Node.js. Node is only used for build, test, differential, and evidence tooling.
All live observations in this section were verified on Illustrator 30.6.0 / ExtendScript 4.5.6 unless stated otherwise.
The implementation isolates compound 32-bit bitwise composition behind simple helper operations. This is intentional: ExtendScript's bitwise/operator parsing behavior can diverge from assumptions made by modern JavaScript optimization passes. The shipped minified and unminified builds are therefore both checked as ES3 and executed live.
A naive $.evalFile() reload would replace the facade and reset the state used by v1/v7 generators. The live suite explicitly verifies that re-evaluating the same compatible ESUUID version preserves the existing facade identity and that a v7 generated after reload remains ordered after the pre-reload value.
A script may load ESUUID before ESRAND and install ESRAND later. ESUUID therefore distinguishes ESRAND from Math.random-fallback capability state. Re-evaluating ESUUID after ESRAND becomes available upgrades the facade instead of preserving a stale fallback backend.
Static parsing is necessary but insufficient for ExtendScript. The live suite clears the existing global facade before loading ESUUID.min.jsx, preventing the reload-preservation path from masking a minifier regression. The minified artifact then runs RFC vectors and ESRAND-backed generation independently.
Portable Node-only validation:
npm ci
npm run verify:portableFull ExtendScript build and offline validation:
npm run verifyIndividual gates:
npm run typecheck
npm run build
npm test
npm run differential
npm run fuzz
npm run benchmark
npm run pack:smoke
npm run evidence:checkMaintainer live gates require a running COM Tool V2 runtime with an Illustrator target:
npm run live-verify -- --pipe <runtime-pipe>
npm run live-benchmark -- --pipe <runtime-pipe> --rounds 3
npm run release:gate -- --pipe <runtime-pipe> --target <target-id>npm run release:gate runs the portable/full verification, the exact 54-check live Illustrator contract, a clean packed-tarball install/rebuild, the evidence/document synchronization gate, and the release manifest/checksum generator. The live benchmark is intentionally separate because its evidence remains valid only while its recorded ESUUID/ESRAND/ESTIMER artifact hashes match the current bytes.
The V2 harness prefers the stable per-user COM Tool V2 install at %LOCALAPPDATA%\Programs\ComToolV2\current\ComTool.Cli.exe, falls back to the toolkit workspace Release CLI when present, and accepts COMTOOL_V2_CLI / --cli as an explicit override. If another COM Tool runtime owns Illustrator, live gates wait safely for up to 60 seconds by default without stealing the lease; set COMTOOL_V2_LEASE_WAIT_MS or --lease-wait-ms to change that bound.
npm run build uses the pinned public ESTC dependency installed by npm ci by default. Set ESUUID_USE_WORKSPACE_ESTC=1 to opt into the sibling toolkit ESTC checkout, ESUUID_ESTC to override the ESTC entry point, or ESUUID_PYTHON to select a Python executable. Standalone live validation can point at external sibling artifacts with ESUUID_ESRAND_VENDOR; live benchmarking additionally accepts ESUUID_ESTIMER_VENDOR.
es-uuid/
├─ src/
│ ├─ core.ts RFC 9562 algorithms and stateful generators
│ ├─ index.ts ESM public exports
│ ├─ jsx-entry.ts ExtendScript global facade
│ ├─ types.ts public TypeScript contracts
│ └─ globals.d.ts ExtendScript build globals
├─ dist/ checked-in generated artifacts
├─ tests/
│ ├─ esuuid-test.mjs unit and RFC vectors
│ ├─ differential.mjs [email protected] parity
│ ├─ fuzz.mjs seeded invariants
│ ├─ benchmark.mjs Node regression microbenchmark
│ ├─ esuuid-live-probe.jsx real-host verification probe
│ └─ live-verify.mjs COM Tool V2 live gate
├─ bench/
│ ├─ live-bench.jsx ESTIMER host benchmark probe
│ └─ run-live-bench.mjs benchmark orchestrator
├─ tooling/
│ ├─ comtool-v2.mjs live-gate transport helper
│ ├─ check-evidence.mjs artifact/evidence/docs consistency gate
│ ├─ pack-smoke.mjs clean tarball rebuild + byte parity
│ ├─ release-gate.mjs end-to-end release orchestrator
│ ├─ release-manifest.mjs hashes + immutable release lock
│ └─ minifier/ pinned conservative JSX minifier pipeline
├─ evidence/
│ ├─ latest-live-verify.json
│ ├─ latest-live-benchmark.json
│ └─ latest-pack-reproducibility.json
├─ release/
│ ├─ esuuid-v0.2.0.lock.json
│ └─ SHA256SUMS.txt
├─ esuuid-build.mjs
├─ extendscript.estc.config.mjs
├─ extendscript.vendor.estc.config.mjs
└─ package.json
- RFC 9562, the UUID specification.
- uuidjs/uuid, used as the differential API/behavior oracle for mappings that make sense in ExtendScript.
- docsforadobe, for ExtendScript and Adobe scripting references.
- ESTC, the TypeScript-to-ExtendScript build and ES3 compatibility toolchain.
- ESRAND, the default Adobe-host entropy backend used by ESUUID.
See ATTRIBUTION.md for the compatibility baseline and licensing notes.
GPL-3.0-or-later. See LICENSE.
ESUUID: RFC 9562 UUID infrastructure for Adobe ExtendScript ES3.