A Kalaallit village with an illustrated antelope carrying a glowing morphology network

oq-api

The experimental JavaScript API for oq’s Kalaallisut morphology and dictionary tools.

API contract · What’s new · Download package · Live import example

Usage

Noun number variants (sg/pl)

Some Kalaallisut noun roots do not disclose number on their own. For example, qimmeq can mean either “a dog” or “dogs”, and qitsuk can mean either “a cat” or “cats”. oq-api keeps both presentation forms in the preset instead of guessing one.

The optional presentationVariants field is keyed by language, number (sg/pl), and determination:

{
  presentationVariants: {
    countability: "count",
    animacy: "animate",
    gender: { da: "common" },
    en: {
      singular: { indefinite: "a dog", definite: "the dog" },
      plural: { indefinite: "dogs", definite: "the dogs" },
    },
    da: {
      singular: { indefinite: "en hund", definite: "hunden" },
      plural: { indefinite: "hunde", definite: "hundene" },
    },
  },
}

morphemeEntryToPreset() preserves this metadata on the preset and its sequence item. glossSummaryItems() accepts numberPreference and determinationPreference, returning the selected form as selectedPresentationVariant while retaining all alternatives. The defaults are singular and indefinite. Consumers can therefore render either “I do not have a dog” or “I do not have dogs”, or show both when the source does not resolve the number. countability is available for future mass-noun rules.

Node.js REPL

Install the public release tarball from your shell, then start node and paste the JavaScript example below:

npm install https://jandahl.github.io/oq-api/downloads/oq-api-0.1-latest.tgz
const {
  API_VERSION, analyzeWord, analyzeWordAsync,
  GRAMMAR_MORPHEMES_URL, SCHEMA_MAJOR_VERSION, mergeMorphemeSources,
} = await import("oq-api");

const response = await fetch(GRAMMAR_MORPHEMES_URL);
const data = await response.json();
const { presets, anyOk } = mergeMorphemeSources(
  [{ status: "fulfilled", value: data }],
  [{ buildable: true, source: "grammarian", schemaMajorVersion: SCHEMA_MAJOR_VERSION }],
);
if (!anyOk) throw new Error("Could not load grammarian presets");

const { matches } = analyzeWord("qimmeqarpoq", presets);
console.log(API_VERSION, matches[0]?.confidence);

const controller = new AbortController();
const { matches: asyncMatches } = await analyzeWordAsync(
  "qimmeqanngilanga", presets, {}, { signal: controller.signal },
);
console.log(asyncMatches.length);

Example output

0.1.1 exact
1
const { getWordClassColors } = await import("oq-api");

const colors = getWordClassColors(["nominal_root"]);
console.log(colors.fill);

For a script file, use a .mjs extension and a static import instead.

API examples

Each snippet uses the Node.js REPL import form. The examples below share this small, real grammarian-shaped fixture for qimmeqarpoq (“he/she/it has a dog”).

Shared example input

const {
  buildWord, morphemeEntryToPreset, toBuilderItem,
} = await import("oq-api");

const rawEntries = [
  {
    id: "qimmeq",
    lexical_facts: { morpheme_type: "stem", word_class: "N", meaning: "dog" },
    application_logic: {
      underlying_form: "qimmeq", continuation_class: "N_POSTBASE",
    },
  },
  {
    id: "N_qaq_Vb",
    lexical_facts: {
      morpheme_type: "derivational_affix",
      category_shift: "N -> V", meaning: "to have",
    },
    application_logic: {
      underlying_form: "-qaq", boundary_behavior: "truncating",
      left_sandhi: "truncative", continuation_class: "V_POSTBASE",
    },
  },
  {
    id: "V_IND_INTR_3SG",
    lexical_facts: {
      morpheme_type: "inflectional_ending",
      meaning: "indicative intransitive, 3sg",
    },
    inflection: {
      mood: "indicative", transitivity: "intransitive",
      subject: { person: 3, number: "sg" },
    },
    application_logic: {
      underlying_form: "-voq", boundary_behavior: "additive",
      continuation_class: "WORD_FINAL",
    },
  },
];

const presets = rawEntries.map(morphemeEntryToPreset);
const sequence = presets.map(({ seq }) => seq[0]);
const items = sequence.map(toBuilderItem);
const result = buildWord(sequence);
const rawWord = result.word;

Example output

sequence.map(({ id, text }) => ({ id, text }))
[
  { id: "qimmeq", text: "qimmeq" },
  { id: "N_qaq_Vb", text: "qaq" },
  { id: "V_IND_INTR_3SG", text: "voq" }
]

Build a word

Convert the shared raw entries to sequence items, then run the complete morphology pipeline:

const { buildWord } = await import("oq-api");

const result = buildWord(sequence);
console.log(result.word, result.approximate, result.closed);

Example output

qimmeqarpoq false true

Incremental engine

For an interactive builder, add already-converted sequence items one at a time:

const { WordBuilder, MORPH_OK } = await import("oq-api");

const builder = new WordBuilder();
builder.init();
for (const item of items) {
  if (builder.add(item) !== MORPH_OK) throw new Error(builder.getError());
}
console.log(builder.getWord());

Example output

qimmeqqaqvoq

Word-class colors

Get stable colors without initializing the oq application:

const { getWordClassColors, WORD_CLASS_THEMES } = await import("oq-api");

const colors = getWordClassColors(["nominal_root"], WORD_CLASS_THEMES.light);
console.log(colors, WORD_CLASS_THEMES.light.name);

Example output

{
  border: "hsl(220,55%,52%)",
  fill: "hsl(220,30%,90%)",
  text: "hsl(220,48%,30%)"
} light

Compose the morphology pipeline

Use the pure lower-level stages when your application needs its own pipeline:

const { applyAllomorphy, validateSequence, respellSurface } = await import("oq-api");

const grammar = validateSequence(sequence);
const surfaceItems = applyAllomorphy(sequence);
const displayWord = respellSurface(rawWord);
console.log(grammar.valid, surfaceItems, displayWord);

Example output

true [ /* allomorph-resolved items */ ] qimmeqarpoq

Deconstruct a word

Search verified morphology analyses, including cancellable asynchronous searches:

const { analyzeWord, analyzeWordAsync } = await import("oq-api");

const { matches } = analyzeWord("qimmeqarpoq", presets);
console.log(matches[0]?.confidence, matches[0]?.rankReasons);

const controller = new AbortController();
const { matches: asyncMatches } = await analyzeWordAsync(
  "qimmeqarpoq", presets, {}, { signal: controller.signal },
);
// cacheAnalysisResult("qimmeqarpoq", asyncMatches);
console.log(asyncMatches.length);

Example output

exact [ "verified_rebuild", "grammarian_decomposition",
  "shorter_morpheme_chain_preferred" ]
1

Analysis breakdown data

Use structured, DOM-free morpheme surface and breakdown information in another UI:

const { resolveMorphemeSurfaces, computeMorphemeBreakdownRows } = await import("oq-api");

const surfaces = resolveMorphemeSurfaces(sequence, result.word);
const rows = computeMorphemeBreakdownRows(items, result.word, sequence);
console.log(surfaces, rows);

Example output

surfaces.map(({ id, surfaceText }) => ({ id, surfaceText }))
[
  { id: "qimmeq", surfaceText: "qimme" },
  { id: "N_qaq_Vb", surfaceText: "qar" },
  { id: "V_IND_INTR_3SG", surfaceText: "poq" }
]

Morpheme data and glosses

Use structured presets, glosses, and source merging without depending on UI code:

const { morphemeEntryToPreset, glossSummaryItems } = await import("oq-api");

const preset = morphemeEntryToPreset(rawEntries[0]);
const glosses = glossSummaryItems(preset.seq);
console.log(preset.id, glosses[0]?.shortGloss);

Example output

root-noun "dog"

Labels and localization

Get display-ready labels in a selected locale:

const { setActiveLocale, resolveMoodLabel, resolvePersonLabel } = await import("oq-api");

setActiveLocale("en");
console.log(resolveMoodLabel("IND").text);
console.log(resolvePersonLabel(3, "SG"));
console.log(resolveMoodLabel("IND", { linguistTerms: true }).text);

Example output

Statement
he
Indicative

Dictionary lookup

Search the published upstream dictionaries with structured options:

const { searchEntries, DICT_SOURCES } = await import("oq-api");

const response = await searchEntries(DICT_SOURCES, "dog", {
  lang: "en", match: "contains", rank: true,
});
console.log(response.results, response.errors);

Example output

[ /* matching entries */ ] []

Browser import

In a browser, import the same API from the public Pages distribution:

import { API_VERSION, getWordClassColors } from
  "https://jandahl.github.io/oq-api/api/v0.1-latest/public-api.js";

console.log(API_VERSION,
  getWordClassColors(["nominal_root"]));

Example output

0.1.1 {
  border: "hsl(210,68%,50%)",
  fill: "hsl(210,38%,9%)",
  text: "hsl(210,52%,76%)"
}

Part of the oq family

oq.spacepope.dk

The main Kalaallisut morphological dictionary and word-builder application.

bl-oq-ly

A block-based learning aid for building and taking apart Kalaallisut words. It is a prototype, not an authoritative source.

retr-oq

A related interactive project by the same author.

Public API URLs

v0.1-latest is a copy of the newest 0.1.x. Downstream can keep that URL until a breaking 0.2.x. Frozen v0.1.N snapshots stay up as well.

Frozen patches

Release tarballs are built from tags, tested in GitHub Actions, and carry provenance attestations.