Gruber

Core

At the centre of Gruber are a set of agnostic modules to help create platform-independent JavaScript servers. They are roughly organised as:

  • Container is a dependency management utility to capture code dependencies, provide them on demand and allow them to be replaced during testing.
  • Migrator is a helper for structuring project migrations that run up or down to manage the state of something like a database.
  • Miscellaneous contains various things that are often useful
  • Store is an abstraction for interacting with asynchonous key-value storage
  • Terminator is a utility for ensuring graceful shutdown of your apps, especially when behind a load-balancer
  • Tokens are an abstraction around signing access to a user to be consumed by other parts of the application

Container

Dependencies type

A utility type for defining a record of dependency-factories

UnwrapDependencies type

A utility type for converting a record of dependency-factories to dependencies

WrapDependencies type

A utility type to convert dependencies back into dependency-factories

Container unstable

Container holds a set of dependencies that are lazily generated from their factories and provides a system to override those dependencies for testing

const container = new Container({
  message: () => 'hello there',
  store: useStore
})

// Retrieve a dependency
console.log(container.get('message')) // outputs "hello there"

// Override dependencies
container.override({
  store: new MemoryStore()
})

// get the overridden store
let store = container.get('store') // MemoryStore

// attempt to get the message
container.get('message') // throws Error('unmet dependency')

// restore the container back to the original dependencies
container.reset()

get

Get a dependency. First checking overrides, then previously computed or finaly use the dependency factory

override

Override the dependencies within the container or create unmet dependencies for those not-provided

// Replace the store with an in-memory one
container.override({ store: new MemoryStore() })

proxy

Create a proxy around an object that injects our dependencies

const container = new Container({ message: () => 'hello there' })

const proxy = container.proxy({ count: 7 })
proxy.message // 'hello there'
proxy.count // 7

// or with object destructuring
const { message, count } = container.proxy({ count: 7 })

reset

Clear any overrides on the dependencies

container.reset()

unwrap internal

Compute a dependency from it's factory

const message = container.unwrap('message')

Migrator

defineMigration

Define a generic migration, this is a generic wrapper around creating a MigrationOptions which within TypeScript you can specify the <T> once, rather than for each action.

const migration = defineMigration({
  up () {},
  down () {},
})

loadMigration

Attempt to load a migration from a file using import.

It combines the name and directory to get a file path, attempts to import-it and convert the default export into a MigrationDefinition. You can also force the <T> parameter onto the definition.

It will throw errors if the file does not exist or if the default export doesn't look like a MigrationOptions.

const migration = await loadMigration(
  '001-create-users.js',
  new URL('./migrations/', import.meta.url)
)

migration.name // "001-create-users.js"
migration.up // function
migration.down // function

MigrationOptions type

Options for defining some sort of migration

const options = {
  up(value) {},
  down(value) {},
}

MigrationDefinition type

A definition for a migration, it's basically a MigrationOptions with a unique name

const migration = {
  name: '042-some-migration',
  up() {},
  down() {},
}

MigrationRecord type

A record of a migration that has been performed

const record = { name: '042-some-migration' }

Migrator

Migrator provides methods for running a specific type of migrations. The idea is that different platforms/integrations can create a migrator that works with a specific feature they want to add migrations around, e.g. a Postgres database.

async function getRecords() {}

async function getDefinitions() {}

async function execute() {}

const migrator = new Migrator({ getRecords, getDefinitions, execute })

See examples/node-fs-migrator

down

Run any "down" migrations for migrations that have already been performed

It would be cool to specify a number here so you could run just 1 but I haven't needed this so it hasn't been properly designed yet

await migrator.up()

up

Run any pending "up" migrations

It would be cool to specify a number here so you could run just 1 but I haven't needed this so it hasn't been properly designed yet

await migrator.up()

Miscellaneous

RandomService type

RandomService provices an abstraction around generating random values

const random // RandomService

// Pick a number between 4 & 7 inclusively
let number = random.number(4, 7)

// Generate a UUID
let uuid = random.uuid()

// Pick an element from an array
let element = random.element([1, 2, 3, 4, 5])

useRandom

A standard implementation of RandomService using Math.random + crypto.randomUUID()

const random = useRandom()
let number = random.number(4, 7)
let uuid = random.uuid()
let element = random.element([1, 2, 3, 4, 5])

formatMarkdownTable

Given a set of records with known columns, format them into a pretty markdown table using the order from columns. If a record does not have a specified value (it is null or undefined) it will be replaced with the fallback value.

const table = formatMarkdownTable(
	[
		{ name: 'Geoff Testington', age: 42 },
		{ name: "Jess Smith", age: 32 },
		{ name: "Tyler Rockwell" },
	],
	['name', 'age'],
	'~'
)

Which will generate:

| name             | age |
| ---------------- | --- |
| Geoff Testington | 42  |
| Jess Smith       | 32  |
| Tyler Rockwell   | ~   |

loader unstable

loader let's you memoize the result of a function to create a singleton from it. It works synchronously or with promises.

let index = 1
const useMessage = loader(() = 'hello there ${i++}')

useMessage() // hello there 1
useMessage() // hello there 1
useMessage() // hello there 1

trimIndentation

trimIndentation takes a template literal (with values) and takes out the common whitespace. Very heavily based on dedent

import { trimIndentation } from "gruber";

console.log(
	trimIndentation`
		Hello there!
		My name is Geoff
	`,
);

Which will output this, without any extra whitespace:

Hello there!
My name is Geoff

preventExtraction

Take steps to prevent an object from being extracted from the app, inspired by crypto.subtle.importKey's extractable parameter.

This will:

  • throw an error if the value are passed to JSON.stringify
  • it recursively applies to nested objects, arrays and items within arrays
  • seal and freeze the value and all nested objects & arrays
const config = preventExtraction({
	name: "Geoff Testington",
	pets: [
		{ name: "Hugo" },
		{ name: "Helga" },
	],
 favourite: {
		mountain: "Cheviot"
	}
})

// Any attempt to JSON-ify will result in an error
console.log(JSON.stringify(config)) // throws a TypeError
console.log(JSON.stringify(config.pets)) // throws a TypeError
console.log(JSON.stringify(config.pets[0])) // throws a TypeError
console.log(JSON.stringify(config.pets[1])) // throws a TypeError
console.log(JSON.stringify(config.favourite)) // throws a TypeError

The value will also be frozen and sealed, so any properties cannot be added, removed or modified.

dangerouslyExpose unstable

DANGER undo a preventExtraction to allow values to be exposed. This undos all of the precations that preventExtraction add.

PromiseList

A dynamic list of promises that are automatically removed when they resolve

const list = new PromiseList()

// Add a promise that waits for 5 seconds
list.push(async () => {
	await new Promise(r => setTimeout(r, 5_000))

	// Add dependant promises too
	list.push(async () => {
		await somethingElse()
	})
})

// Wait for all promises and dependants to resolve in one go
await promises.all()

all

Wait for all promises to be resolved using Promise.all. If new promises are added as a result of waiting, they are also awaited.

await list.all()

length

Get the current number of promises in the list

list.length // 5

push

Add a promise to the list using a factory method, the factory just needs to return a promise

list.push(async () => {
  // ...
})

Store

Store type

Store is an async abstraction around a key-value engine like Redis or a JavaScript Map with extra features for storing things for set-durations

const store // Store

// Store Geoff for 5 minutes
await store.set(
  'users/geoff',
  { name: "Geoff Testington"},
  { maxAge: 5 * 60 * 1_000 }
)

// Retrieve Geoff
const value = await store.get('users/geoff')

// Remove Geoff
await store.delete('users/geoff')

// Close the store
await store.dispose()

// Use Explicit Resource Management to automatically dispose the store
async function main() {
  using store = new MemoryStore(...)

  await store.set('users/geoff', ...)
}

MemoryStore

MemoryStore is a in-memory implementation of Store that puts values into a Map and uses timers to expire data. It was mainly made for automated testing.

const store = new MemoryStore()

Terminator

TerminatorOptions internal type

Options for creating a Terminator instance

const options = {
  // How long to wait in the terminating state so loadbalancers can process it
  timeout: 5_000,

  // Which OS signals to listen for
  signals: ['SIGINT', 'SIGTERM'],

  // Register each signal with the OS and call the handler
  startListeners(signals, handler) {},

  // Exit the process with a given code and optionaly log an error
  exitProcess(statusCode, error) {},
}

Terminator internal

Terminators let you add graceful shutdown to your applications, create one with TerminatorOptions

const arnie = new Terminator({
  timeout: 5_000,
  signals: ['SIGINT', 'SIGTERM'],
  startListeners(signals, handler) {},
  exitProcess(statusCode, error) {},
})

getResponse

Get a Fetch Response with the state of the terminator, probably for a load balancer.

If the terminator is running, it will return a http/200 otherwise it will return a http/503

const response = await arnie.getResponse()

start

Start the terminator and capture a block of code to close the server

arnie.start(async () => {
  await store.dispose()
})

terminate internal

Start the shutdown process

await arnie.terminate(async () => {
  await store.dispose()
})

Tokens

TokenService unstable type

A service for signing and verifying access tokens

CompositeTokens unstable

A TokenService with multiple verification methods and a single signer

debug
{
  "Dependencies": {
    "entrypoint": "core/mod.ts",
    "id": "Dependencies",
    "name": "Dependencies",
    "content": "A utility type for defining a record of dependency-factories",
    "tags": {
      "group": "Container",
      "type": "true"
    },
    "children": {}
  },
  "UnwrapDependencies": {
    "entrypoint": "core/mod.ts",
    "id": "UnwrapDependencies",
    "name": "UnwrapDependencies",
    "content": "A utility type for converting a record of dependency-factories to dependencies",
    "tags": {
      "group": "Container",
      "type": "true"
    },
    "children": {}
  },
  "WrapDependencies": {
    "entrypoint": "core/mod.ts",
    "id": "WrapDependencies",
    "name": "WrapDependencies",
    "content": "A utility type to convert dependencies back into dependency-factories",
    "tags": {
      "group": "Container",
      "type": "true"
    },
    "children": {}
  },
  "Container": {
    "entrypoint": "core/mod.ts",
    "id": "Container",
    "name": "Container",
    "content": "Container holds a set of dependencies that are lazily generated from their factories\nand provides a system to override those dependencies for testing\n\n```js\nconst container = new Container({\n  message: () => 'hello there',\n  store: useStore\n})\n\n// Retrieve a dependency\nconsole.log(container.get('message')) // outputs \"hello there\"\n\n// Override dependencies\ncontainer.override({\n  store: new MemoryStore()\n})\n\n// get the overridden store\nlet store = container.get('store') // MemoryStore\n\n// attempt to get the message\ncontainer.get('message') // throws Error('unmet dependency')\n\n// restore the container back to the original dependencies\ncontainer.reset()\n```",
    "tags": {
      "unstable": "true",
      "group": "Container"
    },
    "children": {
      "override": {
        "id": "Container#override",
        "name": "override",
        "content": "Override the dependencies within the container or create unmet dependencies for those not-provided\n\n```js\n// Replace the store with an in-memory one\ncontainer.override({ store: new MemoryStore() })\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      },
      "reset": {
        "id": "Container#reset",
        "name": "reset",
        "content": "Clear any overrides on the dependencies\n\n```js\ncontainer.reset()\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      },
      "get": {
        "id": "Container#get",
        "name": "get",
        "content": "Get a dependency. First checking overrides, then previously computed or finaly use the dependency factory",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      },
      "unwrap": {
        "id": "Container#unwrap",
        "name": "unwrap",
        "content": "Compute a dependency from it's factory\n\n```js\nconst message = container.unwrap('message')\n```",
        "tags": {
          "internal": "true",
          "group": "Miscellaneous"
        },
        "children": {}
      },
      "proxy": {
        "id": "Container#proxy",
        "name": "proxy",
        "content": "Create a proxy around an object that injects our dependencies\n\n```ts\nconst container = new Container({ message: () => 'hello there' })\n\nconst proxy = container.proxy({ count: 7 })\nproxy.message // 'hello there'\nproxy.count // 7\n\n// or with object destructuring\nconst { message, count } = container.proxy({ count: 7 })\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      }
    }
  },
  "defineMigration": {
    "entrypoint": "core/mod.ts",
    "id": "defineMigration",
    "name": "defineMigration",
    "content": "Define a generic migration, this is a generic wrapper around creating a `MigrationOptions`\nwhich within TypeScript you can specify the `` once, rather than for each action.\n\n```js\nconst migration = defineMigration({\n  up () {},\n  down () {},\n})\n```",
    "tags": {
      "group": "Migrator"
    },
    "children": {}
  },
  "loadMigration": {
    "entrypoint": "core/mod.ts",
    "id": "loadMigration",
    "name": "loadMigration",
    "content": "Attempt to load a migration from a file using `import`.\n\nIt combines the `name` and `directory` to get a file path, attempts to `import`-it and convert the `default` export into a `MigrationDefinition`. You can also force the `` parameter onto the definition.\n\nIt will throw errors if the file does not exist or if the default export doesn't look like a `MigrationOptions`.\n\n\n```js\nconst migration = await loadMigration(\n  '001-create-users.js',\n  new URL('./migrations/', import.meta.url)\n)\n\nmigration.name // \"001-create-users.js\"\nmigration.up // function\nmigration.down // function\n```",
    "tags": {
      "group": "Migrator"
    },
    "children": {}
  },
  "MigrationOptions": {
    "entrypoint": "core/mod.ts",
    "id": "MigrationOptions",
    "name": "MigrationOptions",
    "content": "Options for defining some sort of migration\n\n```js\nconst options = {\n  up(value) {},\n  down(value) {},\n}\n```",
    "tags": {
      "group": "Migrator",
      "type": "true"
    },
    "children": {}
  },
  "MigrationDefinition": {
    "entrypoint": "core/mod.ts",
    "id": "MigrationDefinition",
    "name": "MigrationDefinition",
    "content": "A definition for a migration, it's basically a `MigrationOptions` with a unique name\n\n```js\nconst migration = {\n  name: '042-some-migration',\n  up() {},\n  down() {},\n}\n```",
    "tags": {
      "group": "Migrator",
      "type": "true"
    },
    "children": {}
  },
  "MigrationRecord": {
    "entrypoint": "core/mod.ts",
    "id": "MigrationRecord",
    "name": "MigrationRecord",
    "content": "A record of a migration that has been performed\n\n```js\nconst record = { name: '042-some-migration' }\n```",
    "tags": {
      "group": "Migrator",
      "type": "true"
    },
    "children": {}
  },
  "Migrator": {
    "entrypoint": "core/mod.ts",
    "id": "Migrator",
    "name": "Migrator",
    "content": "Migrator provides methods for running a specific type of migrations.\nThe idea is that different platforms/integrations can create a migrator that\nworks with a specific feature they want to add migrations around, e.g. a Postgres database.\n\n```js\nasync function getRecords() {}\n\nasync function getDefinitions() {}\n\nasync function execute() {}\n\nconst migrator = new Migrator({ getRecords, getDefinitions, execute })\n```\n\nSee [examples/node-fs-migrator](/examples/node-fs-migrator/node-fs-migrator.js)",
    "tags": {
      "group": "Migrator"
    },
    "children": {
      "up": {
        "id": "Migrator#up",
        "name": "up",
        "content": "Run any pending \"up\" migrations\n\n> It would be cool to specify a number here so you could run just 1 but\n> I haven't needed this so it hasn't been properly designed yet\n\n```js\nawait migrator.up()\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      },
      "down": {
        "id": "Migrator#down",
        "name": "down",
        "content": "Run any \"down\" migrations for migrations that have already been performed\n\n> It would be cool to specify a number here so you could run just 1 but\n> I haven't needed this so it hasn't been properly designed yet\n\n```js\nawait migrator.up()\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      }
    }
  },
  "RandomService": {
    "entrypoint": "core/mod.ts",
    "id": "RandomService",
    "name": "RandomService",
    "content": "RandomService provices an abstraction around generating random values\n\n```js\nconst random // RandomService\n\n// Pick a number between 4 & 7 inclusively\nlet number = random.number(4, 7)\n\n// Generate a UUID\nlet uuid = random.uuid()\n\n// Pick an element from an array\nlet element = random.element([1, 2, 3, 4, 5])\n```",
    "tags": {
      "group": "Miscellaneous",
      "type": "true"
    },
    "children": {}
  },
  "useRandom": {
    "entrypoint": "core/mod.ts",
    "id": "useRandom",
    "name": "useRandom",
    "content": "A standard implementation of `RandomService` using Math.random + crypto.randomUUID()\n\n```js\nconst random = useRandom()\nlet number = random.number(4, 7)\nlet uuid = random.uuid()\nlet element = random.element([1, 2, 3, 4, 5])\n```",
    "tags": {
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "Store": {
    "entrypoint": "core/mod.ts",
    "id": "Store",
    "name": "Store",
    "content": "Store is an async abstraction around a key-value engine like Redis or a JavaScript Map\nwith extra features for storing things for set-durations\n\n```js\nconst store // Store\n\n// Store Geoff for 5 minutes\nawait store.set(\n  'users/geoff',\n  { name: \"Geoff Testington\"},\n  { maxAge: 5 * 60 * 1_000 }\n)\n\n// Retrieve Geoff\nconst value = await store.get('users/geoff')\n\n// Remove Geoff\nawait store.delete('users/geoff')\n\n// Close the store\nawait store.dispose()\n\n// Use Explicit Resource Management to automatically dispose the store\nasync function main() {\n  using store = new MemoryStore(...)\n\n  await store.set('users/geoff', ...)\n}\n```",
    "tags": {
      "group": "Store",
      "type": "true"
    },
    "children": {}
  },
  "MemoryStore": {
    "entrypoint": "core/mod.ts",
    "id": "MemoryStore",
    "name": "MemoryStore",
    "content": "MemoryStore is a in-memory implementation of [Store](#store) that puts values into a Map and uses timers to expire data.\nIt was mainly made for automated testing.\n\n```js\nconst store = new MemoryStore()\n```",
    "tags": {
      "group": "Store"
    },
    "children": {}
  },
  "TerminatorOptions": {
    "entrypoint": "core/mod.ts",
    "id": "TerminatorOptions",
    "name": "TerminatorOptions",
    "content": "Options for creating a [Terminator](#terminator) instance\n\n```js\nconst options = {\n  // How long to wait in the terminating state so loadbalancers can process it\n  timeout: 5_000,\n\n  // Which OS signals to listen for\n  signals: ['SIGINT', 'SIGTERM'],\n\n  // Register each signal with the OS and call the handler\n  startListeners(signals, handler) {},\n\n  // Exit the process with a given code and optionaly log an error\n  exitProcess(statusCode, error) {},\n}\n```",
    "tags": {
      "internal": "true",
      "group": "Terminator",
      "type": "true"
    },
    "children": {}
  },
  "Terminator": {
    "entrypoint": "core/mod.ts",
    "id": "Terminator",
    "name": "Terminator",
    "content": "Terminators let you add graceful shutdown to your applications,\ncreate one with [TerminatorOptions](#terminatoroptions)\n\n```js\nconst arnie = new Terminator({\n  timeout: 5_000,\n  signals: ['SIGINT', 'SIGTERM'],\n  startListeners(signals, handler) {},\n  exitProcess(statusCode, error) {},\n})\n```",
    "tags": {
      "internal": "true",
      "group": "Terminator"
    },
    "children": {
      "start": {
        "id": "Terminator#start",
        "name": "start",
        "content": "Start the terminator and capture a block of code to close the server\n\n```js\narnie.start(async () => {\n  await store.dispose()\n})\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      },
      "terminate": {
        "id": "Terminator#terminate",
        "name": "terminate",
        "content": "Start the shutdown process\n\n```js\nawait arnie.terminate(async () => {\n  await store.dispose()\n})\n```",
        "tags": {
          "internal": "true",
          "group": "Miscellaneous"
        },
        "children": {}
      },
      "getResponse": {
        "id": "Terminator#getResponse",
        "name": "getResponse",
        "content": "Get a Fetch Response with the state of the terminator, probably for a load balancer.\n\nIf the terminator is running, it will return a http/200\notherwise it will return a http/503\n\n```js\nconst response = await arnie.getResponse()\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      }
    }
  },
  "TokenService": {
    "entrypoint": "core/mod.ts",
    "id": "TokenService",
    "name": "TokenService",
    "content": "A service for signing and verifying access tokens",
    "tags": {
      "unstable": "true",
      "group": "Tokens",
      "type": "true"
    },
    "children": {}
  },
  "CompositeTokens": {
    "entrypoint": "core/mod.ts",
    "id": "CompositeTokens",
    "name": "CompositeTokens",
    "content": "A TokenService with multiple verification methods and a single signer",
    "tags": {
      "unstable": "true",
      "group": "Tokens"
    },
    "children": {}
  },
  "formatMarkdownTable": {
    "entrypoint": "core/mod.ts",
    "id": "formatMarkdownTable",
    "name": "formatMarkdownTable",
    "content": "Given a set of `records` with known `columns`, format them into a pretty markdown table using the order from `columns`.\nIf a record does not have a specified value (it is null or undefined) it will be replaced with the `fallback` value.\n\n```js\nconst table = formatMarkdownTable(\n\t[\n\t\t{ name: 'Geoff Testington', age: 42 },\n\t\t{ name: \"Jess Smith\", age: 32 },\n\t\t{ name: \"Tyler Rockwell\" },\n\t],\n\t['name', 'age'],\n\t'~'\n)\n```\n\nWhich will generate:\n\n```\n| name             | age |\n| ---------------- | --- |\n| Geoff Testington | 42  |\n| Jess Smith       | 32  |\n| Tyler Rockwell   | ~   |\n```",
    "tags": {
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "loader": {
    "entrypoint": "core/mod.ts",
    "id": "loader",
    "name": "loader",
    "content": "`loader` let's you memoize the result of a function to create a singleton from it.\nIt works synchronously or with promises.\n\n```js\nlet index = 1\nconst useMessage = loader(() = 'hello there ${i++}')\n\nuseMessage() // hello there 1\nuseMessage() // hello there 1\nuseMessage() // hello there 1\n```",
    "tags": {
      "unstable": "true",
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "trimIndentation": {
    "entrypoint": "core/mod.ts",
    "id": "trimIndentation",
    "name": "trimIndentation",
    "content": "`trimIndentation` takes a template literal (with values) and takes out the common whitespace.\nVery heavily based on [dedent](https://github.com/dmnd/dedent/tree/main)\n\n```js\nimport { trimIndentation } from \"gruber\";\n\nconsole.log(\n\ttrimIndentation`\n\t\tHello there!\n\t\tMy name is Geoff\n\t`,\n);\n```\n\nWhich will output this, without any extra whitespace:\n\n```\nHello there!\nMy name is Geoff\n```",
    "tags": {
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "preventExtraction": {
    "entrypoint": "core/mod.ts",
    "id": "preventExtraction",
    "name": "preventExtraction",
    "content": "Take steps to prevent an object from being extracted from the app,\ninspired by crypto.subtle.importKey's [extractable](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#extractable) parameter.\n\nThis will:\n- throw an error if the value are passed to JSON.stringify\n- it recursively applies to nested objects, arrays and items within arrays\n- [seal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal) and [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) the value and all nested objects & arrays\n\n```js\nconst config = preventExtraction({\n\tname: \"Geoff Testington\",\n\tpets: [\n\t\t{ name: \"Hugo\" },\n\t\t{ name: \"Helga\" },\n\t],\n favourite: {\n\t\tmountain: \"Cheviot\"\n\t}\n})\n\n// Any attempt to JSON-ify will result in an error\nconsole.log(JSON.stringify(config)) // throws a TypeError\nconsole.log(JSON.stringify(config.pets)) // throws a TypeError\nconsole.log(JSON.stringify(config.pets[0])) // throws a TypeError\nconsole.log(JSON.stringify(config.pets[1])) // throws a TypeError\nconsole.log(JSON.stringify(config.favourite)) // throws a TypeError\n```\n\nThe value will also be frozen and sealed, so any properties cannot be added, removed or modified.",
    "tags": {
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "dangerouslyExpose": {
    "entrypoint": "core/mod.ts",
    "id": "dangerouslyExpose",
    "name": "dangerouslyExpose",
    "content": "**DANGER** undo a [preventExtraction](#preventextraction) to allow values to be exposed.\nThis undos all of the precations that `preventExtraction` add.",
    "tags": {
      "unstable": "true",
      "group": "Miscellaneous"
    },
    "children": {}
  },
  "PromiseList": {
    "entrypoint": "core/mod.ts",
    "id": "PromiseList",
    "name": "PromiseList",
    "content": "A dynamic list of promises that are automatically removed when they resolve\n\n```js\nconst list = new PromiseList()\n\n// Add a promise that waits for 5 seconds\nlist.push(async () => {\n\tawait new Promise(r => setTimeout(r, 5_000))\n\n\t// Add dependant promises too\n\tlist.push(async () => {\n\t\tawait somethingElse()\n\t})\n})\n\n// Wait for all promises and dependants to resolve in one go\nawait promises.all()\n\n```",
    "tags": {
      "group": "Miscellaneous"
    },
    "children": {
      "push": {
        "id": "PromiseList#push",
        "name": "push",
        "content": "Add a promise to the list using a factory method,\nthe `factory` just needs to return a promise\n\n```js\nlist.push(async () => {\n  // ...\n})\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      },
      "all": {
        "id": "PromiseList#all",
        "name": "all",
        "content": "Wait for all promises to be resolved using `Promise.all`.\nIf new promises are added as a result of waiting, they are also awaited.\n\n```js\nawait list.all()\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      },
      "length": {
        "id": "PromiseList#length",
        "name": "length",
        "content": "Get the current number of promises in the list\n\n```js\nlist.length // 5\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "children": {}
      }
    }
  }
}