Overview

The package layout, the type system, and how to read this reference.

The Go package embeds a WebAssembly build of the core and calls it through wazero, a runtime written in pure Go — no cgo required, with cross-compilation and static linking left intact.

This section is the complete Go API reference — every exported function, type and method has its own entry.

Install

go get github.com/x-haose/x-iztro/go/iztro
import "github.com/x-haose/x-iztro/go/iztro"

Your first chart

chart, err := iztro.BySolar("2000-8-16", 2, iztro.GenderFemale, true, iztro.LanguageEnUS, nil)
if err != nil {
    log.Fatal(err)
}

fmt.Println(chart.SolarDate, chart.LunarDate)
// 2000-8-16 二〇〇〇年七月十七

soul := chart.Palace(iztro.PalaceSoul)
if len(soul.MajorStars) > 0 {
    fmt.Println(soul.MajorStars[0].Name)
    // emperor
} else {
    fmt.Println("the Soul palace is empty, borrowing from the opposite palace:", soul.OppositePalace().MajorStars[0].Name)
}

LunarDate stays in Chinese under every language — it is a lunar date written in Chinese numerals. 二〇〇〇年七月十七 is the 17th day of the 7th lunar month of 2000.

The major-star list can be empty

A chart usually has two palaces with no major star (empty palaces), and the Soul palace may well be one of them. Writing soul.MajorStars[0] straight out panics on such a chart — check the length first, or branch on IsEmpty and borrow from the opposite palace.

Package layout

The package is flat; everything exported lives under iztro. By topic:

TopicMain exportsPage in this reference
ChartingBySolar, ByLunar, RearrangedCharting entries
Data typesAstrolabe, Palace, Star, Horoscope, ConfigThe four pages from Astrolabe object onward
Key constantsPalaceSoul, StarZiweiMaj, MutagenLu and so onData tables
Lightweight queriesGetZodiacBySolarDate and friendsLightweight queries
UtilitiesFixIndex, GetBrightness and friendsUtilities
Star placementGetMajorStar, GetHoroscopeStar and friendsStar placement
Data tablesStarsInfo, HeavenlyStems and friendsData tables
TranslationTranslate, KeyOf, KeyOfInTranslation
Errors*Error, the Err* sentinels, the Code* constantsError handling
RuntimeWarmup, Close, CompilationCacheDirFurther down this page

Constants are the keys

The key constants in the package have the language-independent keys as their values, so they compare directly against the *Key fields on the data objects:

soul := chart.Palace(iztro.PalaceSoul)
fmt.Println(soul.MajorStars[0].Key == iztro.StarZiweiMaj)
// true

They are all untyped string constants, so string literals work just as well — chart.Palace("soulPalace") and chart.Palace(iztro.PalaceSoul) are equivalent. The constants earn their keep through IDE completion and spell checking.

Test on Key, never on Name

star.Name varies with the charting language (紫微 on a Chinese chart, emperor on an English one); star.Key is ziweiMaj under any language. Every predicate should rest on the *Key fields or on the built-in predicate methods.

Variadic parameters

Methods taking a list of stars or mutagens are variadic throughout, so call sites need not build a slice:

soul := chart.Palace(iztro.PalaceSoul)
target := chart.Palace(iztro.PalaceWealth)

fmt.Println(soul.Has(iztro.StarZiweiMaj, iztro.StarTianxiangMaj))
fmt.Println(soul.FliesTo(target, iztro.MutagenLu, iztro.MutagenJi))

Output

false
false

Expand an existing slice with ...:

soul := chart.Palace(iztro.PalaceSoul)
stars := []string{iztro.StarZiweiMaj, iztro.StarTianxiangMaj}

fmt.Println(soul.Has(stars...))

Output

false

Horoscope scope-star queries are the exception

The star parameter of the HasHoroscopeStars family is a []string rather than variadic, because two string parameters (palace name and scope) already precede it and a variadic list would make call sites ambiguous.

Error handling

Entry points that compute something return (value, error); pure query methods (Palace, Star, Has and so on) return no error and give nil or a zero value when nothing is found.

_, err := iztro.BySolar("2000-13-1", 2, iztro.GenderFemale, true, iztro.LanguageEnUS, nil)

fmt.Println(err)
fmt.Println(errors.Is(err, iztro.ErrInvalidDate))

var e *iztro.Error
if errors.As(err, &e) {
    fmt.Println(e.Code)
}

Output

iztro: invalid solar date '2000-13-1': month must be within 1-12
true
invalid_date

Every error is an *iztro.Error carrying a machine-readable Code, and errors.Is matches it against the four sentinels. See Error handling.

Runtime and performance

The wasm module is compiled once and instantiated on demand: each instance owns its linear memory, so concurrent calls take an instance each rather than sharing one behind a lock, and the number of instances is capped at GOMAXPROCS. There is no global mutex on the charting hot path — instances are handed out over a channel, and the lock is taken only when the runtime first initializes and on Close. Several goroutines charting at once therefore run genuinely in parallel: on the same ten-core machine, 8 goroutines running 800 charts finish more than four times faster than a single goroutine.

The compiled machine code is cached on disk under the user cache directory (CompilationCacheDir reports it), bucketed by the hash of the wasm contents, so a new wasm naturally lands in a new bucket.

Measured magnitudes (Apple M series, 10 cores; the exact figures move with the machine and the size of the wasm):

StageTime
First call, compilation cache miss (the wasm has to be compiled)one or two hundred milliseconds
First call, compilation cache hittwenty to thirty milliseconds
Steady-state single chartaround half a millisecond

The cost is JSON encoding, not the computation

Most of that steady-state half millisecond goes on serializing the whole chart to JSON on the wasm side and deserializing it back into structs on the Go side, not on the Zi Wei Dou Shu computation itself. When you only need a field or two, a lightweight query (GetMajorStarBySolarDate and friends) is far cheaper than charting in full.

Warmup / Close / CompilationCacheDir

func Warmup(ctx context.Context) error
func Close(ctx context.Context) error
func CompilationCacheDir(ctx context.Context) (string, error)
FunctionDescription
WarmupCompiles ahead of time and fills the instance pool, moving the cold-start cost into startup. Skipping it is fine — compilation and instantiation are lazy anyway
CloseShuts the runtime down and returns all instance memory. Rarely needed; calling any function in this package afterwards re-initializes it automatically
CompilationCacheDirReturns the compilation cache directory; empty string when the on-disk cache is not enabled
ctx := context.Background()

if err := iztro.Warmup(ctx); err != nil {
    log.Fatal(err)
}

dir, err := iztro.CompilationCacheDir(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Println(dir != "")

chart, err := iztro.BySolar("2000-8-16", 2, iztro.GenderFemale, true, iztro.LanguageEnUS, nil)
if err != nil {
    log.Fatal(err)
}
fmt.Println(chart.SolarDate)

Output

true
2000-8-16

A service process that wants its very first request on the hot path only has to call Warmup once during startup.

Context variants

The entry points that cross into wasm — charting, horoscope, rearranging, text projection — each have a *Context version taking one extra context.Context:

Without ctxWith ctx
BySolar / ByLunarBySolarContext / ByLunarContext
Astrolabe.Horoscope / HoroscopeNowHoroscopeContext / HoroscopeNowContext
Astrolabe.RearrangedRearrangedContext
Astrolabe.ToText / Horoscope.ToText / PalaceToText / SurroundedPalacesToTextToTextContext / PalaceToTextContext / SurroundedPalacesToTextContext
Warmup / Close / CompilationCacheDir exist only in the ctx form
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

chart, err := iztro.BySolarContext(ctx, "2000-8-16", 2, iztro.GenderFemale, true, iztro.LanguageEnUS, nil)
if err != nil {
    log.Fatal(err)
}
fmt.Println(chart.SolarDate)

Output

2000-8-16

ctx governs the queue, not the computation

ctx cancels the wait for a free wasm instance. Once an instance is in hand the computation on the wasm side cannot be interrupted — a single chart is sub-millisecond to begin with, so there is no long task to break off.

The wazero compiler covers only amd64 and arm64

On those two architectures wazero uses its optimizing compiler (down to machine code). Everything else falls back to the interpreter, which still produces correct results but is more than an order of magnitude slower. Deploy on amd64 or arm64 in production.

How to read an entry

Every API entry is organized into the same eight sections:

Purpose — one sentence on what it does
Zi Wei meaning — the concept it corresponds to in Zi Wei Dou Shu (omitted for purely engineering functions)
Signature — lifted verbatim from the source
Parameters — name, type, whether required, default, description
Return value — type and structure
Example — a snippet you can run as-is
Output — the real result of running that example
Edge cases and pitfalls — empty values, out-of-range input, configuration effects, interactions with other APIs

Examples all use the same chart — a female born 16 August 2000 in the Tiger hour — so they can be compared across pages. The full data for that chart is on the data model.

On this page