docs: add a CONTRIBUTING file with basic instructions

This commit is contained in:
Lukas Obermann
2026-03-31 15:24:33 +02:00
parent 1c4d8b095e
commit 5fe6b85c85
3 changed files with 136 additions and 47 deletions
+80
View File
@@ -0,0 +1,80 @@
# Contribute
## Prerequisites
You will need Node.js installed. Install dependencies via `npm install`/`npm i`.
## Development
### Overview
There are three areas of importance when adding new entity descriptions or editing existing ones.
- `src/index.ts`: Register new entity descriptions in the `registeredEntityDescriptionCreators` object.
- `src/entities`: This folder contains files with entity descriptors. You will find all existing ones in files directly within this folder. If you want to add a new entity descriptor, append to a fitting file or create a new one here.
- `src/entities/partial`: This folder contains reusable helper functions for entity descriptors.
### `Reader` and `StdReader`
Sometimes, always having to explicitly pass functions and values a function depends on can be quite cumbersome. This is where `Reader<E, T>` comes in. Essentially, it serves as a _dependency injection_ mechanism. You can _ask_ (via `Reader.ask`) for the whole value of the environment (`E`) or extract specific parts via `Reader.asks`. Providing the values by calling the `run` method on a `Reader` instance will return a value of type `T`.
Since the environment values are quite similar across the project, there is a specific type `StdReader<T, K, E, AE, CE>` with an associated type `StdEnv<K, E, AE, CE>` that makes dealing with the same environment parts way easier and shorter. `StdReader` is in fact just a shortcut of combining `Reader` and `StdEnv`. But `StdEnv`s type arguments need to be explained.
- `K`: A set of abbreviations of the keys required for this environment. For example, `"t"` corresponds to the `translate` function and `"ibi"` to the `getInstanceById` function. See the `EnvMapAbbr` type in `src/entities/partial/reader.ts` for all available abbreviations. If you need multiple values from `StdEnv`, separate them with pipes. For example `"t" | "ibi"` requires both `translate` and `getInstanceById`. If you use `ibi`, `ai` (`getAllInstances`), or `acibp` (`getAllChildInstancesByParent`), you will need to provide values for the type parameters `E`, `AE`, and `CE`, respectively. Type parameters you do not need should be set to `never`.
- `E`: The set of entity names you want to request single instances from. If you need to get instances from multiple entities, separate them with pipes.
- `AE`: The set of entity names you want to request all instances from. If you need to get instances from multiple entities, separate them with pipes.
- `CE`: The set of child entity names you need to get instances from for a specific parent instance (e.g. getting all enhancements for a spell).
There are a lot of functions that wrap `Reader.asks` already, which makes working with `Reader`s way more straightforward.
#### Examples
Direct use for a translation string:
```ts
const translationReader = Reader.asks((env: StdEnv<"t">) =>
env.translate("Specific translation string"),
)
// somewhere up higher in the call hierarchy:
const actualTranslation = translationReader.run({ translation })
```
Translate with helper functions:
```ts
const translationReader = translateR("Specific translation string")
```
_Note:_ The `R` suffix for helper functions makes clear that it is returning a `Reader` instance instead of the final value.
#### Working with `Reader` instances
`Reader` instances need to be passed up to calling functions to that the uppermost function returns a `Reader` that contains the environment for all values it needs to produce.
You cannot modify a value from a `Reader` directly. Instead, use the `map` method to provide a function that modifies the value. To chain multiple `Reader`-producing functions together, use the `then` method. The function that gets passed to it receives the result from the previous reader and can return a new `Reader` with the same environment type. Use the `thenW` method to return a `Reader` with a different environment; the two environments will be merged.
To work with `Reader` instances in arrays, use `Reader.sequence` or `Reader.traverse`. The former one combines an array of `Reader`s into a single `Reader` of all the results of the array, the latter one applies a `Reader`-returning function to all elements of the array and then combines all results into a single `Reader`.
## Building
To build once or build whenever there are changes, use the following command:
- **One-time build:** `npm run build`
- **Continuous building (watch mode):** `npm run watch`
## Testing
To check if the output matches your expectations, you can run the `testOutput.ts` script in the `scripts` folder. From the project root, call it the following way:
```sh
node ./scripts/testOutput.ts -d ../path/to/data <locale> <entity> <instance>
```
Replace values as appropriate:
- `../path/to/data` is the path to a local copy of the `elyukai/optolith-data` repository
- `<locale>` is a locale identifier that exists in the database (e.g. `de-DE`)
- `<entity>` is a valid entity name (e.g. `Spell`)
- `<instance>` is a UUID of an instance of the specified entity
This will print a formatted textual representation to the terminal.
+26 -26
View File
@@ -627,9 +627,9 @@
}
},
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
"integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -637,7 +637,7 @@
"balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
"node": "18 || 20 || >=22"
}
},
"node_modules/@eslint/config-array/node_modules/minimatch": {
@@ -1119,9 +1119,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1332,9 +1332,9 @@
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2366,9 +2366,9 @@
}
},
"node_modules/eslint/node_modules/brace-expansion": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
"integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -2376,7 +2376,7 @@
"balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
"node": "18 || 20 || >=22"
}
},
"node_modules/eslint/node_modules/escape-string-regexp": {
@@ -2956,9 +2956,9 @@
"license": "ISC"
},
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
"version": "4.7.9",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
"integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4010,9 +4010,9 @@
"license": "MIT"
},
"node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.1.tgz",
"integrity": "sha512-fvU78fIjZ+SBM9YwCknCvKOUKkLVqtWDVctl0s7xIqfmfb38t2TT4ZU2gHm+Z8xGwgW+QWEU3oQSAzIbo89Ggw==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -4050,9 +4050,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5272,9 +5272,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"dev": true,
"license": "ISC",
"bin": {
+30 -21
View File
@@ -6,7 +6,7 @@ import type { MessageValue } from "messageformat/functions"
import { findPackageJSON } from "node:module"
import { dirname, join } from "node:path"
import { argv } from "node:process"
import { styleText, type InspectColor } from "node:util"
import { parseArgs, styleText, type InspectColor, type ParseArgsOptionsConfig } from "node:util"
import { schema } from "optolith-database-schema"
import { createCache, type IdMap as CacheIdMap } from "optolith-database-schema/cache"
import { TSONDB } from "tsondb"
@@ -20,27 +20,26 @@ import {
type TableEntityDescriptionSection,
} from "../lib/index.js"
const dataRootPath = join(
dirname(findPackageJSON(import.meta.url) ?? import.meta.filename),
"..",
"client",
"src",
"database",
"contents",
"data",
)
const options = {
data: {
type: "string",
short: "d",
default: join(
dirname(findPackageJSON(import.meta.url) ?? import.meta.filename),
"..",
"client",
"src",
"database",
"contents",
"data",
),
},
} satisfies ParseArgsOptionsConfig
const db = await TSONDB.create({
schema,
dataRootPath,
locales: ["de-DE"],
})
const [localeId, entity, id] = argv.slice(2)
if (!entity || !db.schema.isEntityName(entity) || !isSupportedEntity(entity)) {
throw new Error("Invalid entity name")
}
const {
values: { data: dataRootPath },
positionals: [localeId, entity, id],
} = parseArgs({ args: argv.slice(2), options, allowPositionals: true })
if (localeId === undefined) {
throw new Error("No locale provided")
@@ -50,6 +49,16 @@ if (!id) {
throw new Error("No ID provided")
}
const db = await TSONDB.create({
schema,
dataRootPath,
locales: [localeId],
})
if (!entity || !db.schema.isEntityName(entity) || !isSupportedEntity(entity)) {
throw new Error("Invalid entity name")
}
const localeInstance = db.getInstanceOfEntityById("Locale", localeId)
if (localeInstance === undefined) {