static-tree
This package is part of the static-tree
monorepo
Table of Contents
Installation
npm install static-tree
yarn add static-tree
pnpm add static-tree
Quick Start
-
Use the idiomatic tBuild
to create a static tree. See tBuild for examples and options.
import { tBuild } from 'static-tree';
const { node: api } = tBuild('api', {
pathResolver: () => 'https://api.domain.example',
build: (builder) =>
builder
.addChild('auth', {
build: (builder) =>
builder
.addChild('logout')
.addChild('oauth', {
build: (builder) => builder.addChild('google').addChild('discord'),
})
})
.addChild('camelCaseKey', {
pathResolver: () => 'camel-case-key',
})
.addChild(':organization', {
pathResolver: (_node, arg) => arg,
build: (builder) =>
builder.addChild(':user', {
pathResolver: (_node, arg) => arg,
}),
}),
});
The declaration above will produce this tree structure:
api (resolve statically to 'https://api.domain.example' at runtime)
|
|-- auth
. |-- logout
. |-- oauth
. |
. |-- google
. |-- discord
|-- camelCaseKey (resolved statically to 'camel-case-key' at runtime)
|-- :organization (resolved dynamically to the given argument at runtime)
|
|-- :user (resolved dynamically to the given argument at runtime)
-
Access type-safe nested children
root.auth.oauth.google.$.path();
root[':organization'][':user'].$.path({
args: {
':organization': 'test-org',
':user': 'test-user',
}
});
root.auth.logout.$.depth();
root.auth.oauth.discord.$.root()
the $
getter returns the TNodePublicApi collection of methods.
-
Access type-safe data
root.$.data();
root.nestedChild.$.data().nestedChildData;
Documentation & Terminologies
This repo includes a full api extracted documentation generated by @microsoft/api-extractor & @microsoft/api-documenter. Please refer to said docs for examples and details.
Original Use Case
This package was derived from the solution to a specific problem I encountered frequently. Consider having this "config" object:
const AppConfig = {
urls: {
web: 'https://domain.example',
api: {
index: 'https://api.domain.example',
auth: {
index: '/auth',
logout: '/logout',
oauth: {
index: '/oauth',
google: '/google',
},
},
},
},
};
To get a full api url of google oauth, we have to do quite a lot:
const { api: { auth, index } } = AppConfig.urls;
const path = index + auth.index + auth.index.oauth.index + auth.oauth.google;
Already there are some problems:
-
Verbosity: lots of reference to get to something, more typing equals more typos equals less productive time.
-
The inconsistency of the config structure: some path will require an object index
, some path is just a string. We could refactor to something more predictable, although i think we can agree that this would quickly get out of hand and is very disorienting to look at:
const AppConfig = {
urls: {
web: {
base: 'https://domain.example',
paths: {},
},
api: {
base: 'https://api.domain.example',
paths: {
auth: {
base: '/auth',
paths: {
logout: {
base: '/logout',
},
},
},
},
},
},
};
Introducing static-tree
, arguably a better alternative to the above.
import { tBuild } from 'static-tree';
const { node: api } = tBuild('api', {
pathResolver: () => 'https://api.domain.example',
build: (builder) => builder
.addChild('auth', {
build: (builder) => builder
.addChild('logout')
.addChild('oauth', {
build: (builder) => builder
.addChild('google')
.addChild('discord'),
}),
}),
});
You might say, why the ugly builder pattern? Because I have not figured out any other pattern that allows the same level of type-safety. It seems like a lot for what would be an object declaration, but consider what we can do now:
api.auth.oauth.google.$.path();
Even more cool things (and perhaps more in the future if we need to extend the api):
api.auth.oauth.google.$.path({ depth: 2 });
api.auth.oauth.google.$.path({ depth: -2 });