open-index/open-npm huggingface dataset:
npm package: @prairielearn/flash
Description: Adds support for flash messages to Express applications.
Latest version: 3.0.2

# `@prairielearn/flash`

Adds support for flash messages to Express applications.

## Usage

`req.session` must exist. In PrairieLearn, this is provided by the [`@prairielearn/session`](../session/README.md) package. In other applications, [`express-session`](https://www.npmjs.com/package/express-session) is a common way to implement sessions in Express applications.

```ts
import express from 'express';
import session from 'express-session';
import { flashMiddleware } from '@prairielearn/flash';

const app = express();

app.use(
  session({
    // See https://www.npmjs.com/package/express-session for more information
    // about configuring the session middleware.
    secret: 'secret',
    resave: false,
    saveUninitialized: true,
  }),
);
app.use(flashMiddleware());
```

Now, you can use the `flash()` function exported by `@prairielearn/flash` to read/write flash messages.

```ts
import { flash } from '@prairielearn/flash';

app.get('/set-flash', (req, res) => {
  flash('notice', 'Your preferences have been updated.');
  flash('success', 'Course created successfully.');
  flash('warning', 'Syncing completed with 5 warnings.');
  flash('error', html`Group must have <em>fewer than 10 members</em>.`);

  res.redirect('/display-flash');
});

app.get('/display-flash', (req, res) => {
  const messages = flash();
  res.json(messages);
});
```

The `flash()` function has three behaviors:

- `flash(type: string, message: string | HtmlSafeString)`: Set a message with the given type.
- `flash(type: string): FlashMessage[]`: Get all messages with the given type.
- `flash(types: string[]): FlashMessage[]`: Get all messages with any of the given types.
- `flash(): FlashMessage[]`: Get all messages.

Once a message is read, it is immediately removed from the persisted list of messages. A message is _only_ removed once it is read; it will be persisted indefinitely if it iw not.
npm package: @prairielearn/html
Description: Utilities for easily rendering HTML from within JavaScript.
Latest version: 5.0.2

# `@prairielearn/html`

Utilities for easily rendering HTML from within JavaScript.

## Usage

The `html` tagged template literal can be used to render HTML while ensuring that any interpolated values are properly escaped.

By convention, HTML templates are located in `*.html.ts` files.

```ts
// Hello.html.ts
import { html } from '@prairielearn/html';

export function Hello({ name }: { name: string }) {
  return html`<div>Hello, ${name}!</div>`;
}
```

This can then be used to render a string:

```ts
import { Hello } from './Hello.html.ts';

console.log(Hello({ name: 'Anjali' }).toString());
// Prints "<div>Hello, Anjali!</div>"
```

### Using escaped HTML

If you want to pre-escape some HTML, you can wrap it in `escapeHtml` to avoid escaping it twice. This is useful if you want to inline some HTML into an attribute, for instance with a Bootstrap popover.

```ts
import { html, escapeHtml } from '@prairielearn/html';

console.log(html`
  <button data-bs-toggle="popover" data-bs-content="${escapeHtml(html`<div>Content here</div>`)}">
    Open popover
  </button>
`);
```

## Why not EJS?

In the past, PrairieLearn used EJS to render most views. However, using a tagged template literal and pure JavaScript to render views has a number of advantages:

- Prettier will automatically format the content of any `html` tagged template literal; EJS does not have any automatic formatters.
- Authoring views in pure JavaScript allows for easier and more explicit composition of components.
- It's possible to use ESLint and TypeScript to type-check JavaScript views; EJS does not offer support for either.
npm package: @prairielearn/html-ejs
Description: Utilities for rendering EJS templates for use with the `@prairielearn/html` package.
Latest version: 2.0.24

# `@prairielearn/html-ejs`

Utilities for rendering EJS templates for use with the `@prairielearn/html` package.

If you have an EJS partial that you'd like to use inside of an `html` tagged template literal, you can use the `renderEjs` helper:

```html
<!-- hello.ejs -->
Hello, <%= name %>!
```

```ts
import { html } from '@prairielearn/html';
import { renderEjs } from '@prairielearn/html-ejs';

console.log(
  html`
    <div>Hello, world!</div>
    <div>${renderEjs(import.meta.url, "<%- include('./hello'); %>", { name: 'Anjali' })}</div>
  `.toString(),
);
```
npm package: @prairielearn/logger
Description: Provides a shared [Winston](https://github.com/winstonjs/winston) instance for all logging.
Latest version: 3.1.1

# `@prairielearn/logger`

Provides a shared [Winston](https://github.com/winstonjs/winston) instance for all logging.

## Usage

```ts
import { logger, addFileLogging } from '@prairielearn/logger';

// Log all messages to a file.
addFileLogging({ filename: '/path/to/file.log' });

// Log all errors to another file.
addFileLogging({ filename: '/path/to/errors.log', level: 'error' });

logger.debug('verbose');
logger.verbose('verbose');
logger.info('info');
logger.warn('warn');

try {
  await mightError();
} catch (err) {
  // When logging an error, ensure that the first argument is a string. You can
  // pass the error object as the second argument if desired.
  logger.error('An error occurred', err);
}
```
npm package: @prairielearn/named-locks
Description: Uses Postgres row-level locks to grant exclusive access to resources.
Latest version: 4.0.3

# `@prairielearn/named-locks`

Uses Postgres row-level locks to grant exclusive access to resources.

## Usage

You must first call `init` with Postgres connection details and an idle error handler:

```ts
import { init } from '@prairielearn/named-locks';

await init(
  {
    user: 'postgres',
    // ... other Postgres config
  },
  (err) => {
    throw err;
  },
);
```

You can then obtain a lock and do work while it is held. The lock will be automatically released once the provided function either resolves or rejects.

```ts
import { doWithLock } from '@prairielearn/named-locks';

await doWithLock('name', {}, async () => {
  console.log('Doing some work');
});
```

Optionally, you may configure the lock to automatically "renew" itself by periodically making no-op queries in the background. This is useful for operations that may take longer than the configured Postgres idle session timeout.

```ts
import { doWithLock } from '@prairielearn/named-locks';

await doWithLock(
  'name',
  {
    autoRenew: true,
  },
  async () => {
    console.log('Doing some work');
  },
);
```
npm package: @prairielearn/node-metrics
Description: A package to collect Node metrics (CPU, memory, etc.) and report them to AWS CloudWatch.
Latest version: 3.0.4

# `@prairielearn/node-metrics`

A package to collect Node metrics (CPU, memory, etc.) and report them to AWS CloudWatch.

## Usage

```ts
import * as nodeMetrics from '@prairielearn/node-metrics';

// Start collecting and reporting metrics to CloudWatch:
nodeMetrics.start({
  awsConfig: {
    region: 'us-east-2',
    // ...
  },
  intervalSeconds: 10,
  dimensions: [{ Name: 'InstanceId', Value: 'i-1234567890abcdef0' }],
  onError(err) {
    console.error('Error reporting Node metrics:', err);
    // Report to Sentry, etc.
  },
});

// Later, when you're done:
nodeMetrics.stop();
```
npm package: @prairielearn/preact-cjs
Description: This package re-exports all of Preact's exports, but _only_ the CJS versions. We can't rely on the original package, which provides both ESM and CJS exports, because we have a mixed ESM/CJS codebase and the CJS/ESM versions of Preact don't share the neces
Latest version: 2.0.0

# `@prairielearn/preact-cjs`

This package re-exports all of Preact's exports, but _only_ the CJS versions. We can't rely on the original package, which provides both ESM and CJS exports, because we have a mixed ESM/CJS codebase and the CJS/ESM versions of Preact don't share the necessary singleton state with each other. So, we need to ensure that we always use the CJS version of Preact.
npm package: @prairielearn/preact-cjs-compat
Description: This package is an alternative to `@preact/compat` that works with `@prairielearn/preact-cjs`.
Latest version: 1.0.6

# `@prairielearn/preact-cjs-compat`

This package is an alternative to `@preact/compat` that works with `@prairielearn/preact-cjs`.
npm package: @prairielearn/run
Description: A small utility for emulating expression-oriented programming, inspired by <https://maxgreenwald.me/blog/do-more-with-run>.
Latest version: 2.0.2

# `@prairielearn/run`

A small utility for emulating expression-oriented programming, inspired by <https://maxgreenwald.me/blog/do-more-with-run>.

## Usage

```ts
import { run } from '@prairielearn/run';

const x = run(() => {
  if (foo()) return 1;
  if (bar()) return 2;
  return 3;
});
```

This is equivalent to the following:

```ts
let x;

if (foo()) {
  x = 1;
} else if (bar()) {
  x = 2;
} else {
  x = 3;
}
```

It's also equivalent to this:

```ts
const x = foo() ? 1 : bar() ? 2 : 3;
```

While the nested ternary is more concise in this example, it's more difficult to read and maintain as the number and complexity of conditions grows.

## Why?

Max Greenwald has a great blog post that explains the rationale for this pattern: <https://maxgreenwald.me/blog/do-more-with-run>

Why is this one-liner a package? Mostly so the documentation and rationale can be kept in one place.
npm package: @prairielearn/sanitize
Description: A collection of functions for sanitizing and escaping various values.
Latest version: 3.0.2

# `@prairielearn/sanitize`

A collection of functions for sanitizing and escaping various values.

## Usage

```ts
import {
  sanitizeObject,
  escapeRegExp,
  truncate,
  recursivelyTruncateStrings,
} from '@prairielearn/sanitize';

sanitizeObject({
  value: 'null \u0000 byte',
});

escapeRegExp('foo*(bar)');

truncate('testing testing', 7);

recursivelyTruncateStrings(
  {
    foo: {
      bar: {
        baz: 'biz'.repeat(10000),
      },
    },
  },
  100,
);
```
npm package: @prairielearn/sentry
Description: Opinionated wrapper around `@sentry/core` and `@sentry/node-core`. The main modification is an async `init` function that automatically sets the release to the current Git revision, if available.
Latest version: 5.0.3

# `@prairielearn/sentry`

Opinionated wrapper around `@sentry/core` and `@sentry/node-core`. The main modification is an async `init` function that automatically sets the release to the current Git revision, if available.

```ts
import { init } from '@prairielearn/sentry';

await init({
  dsn: 'DSN HERE',
  environment: 'ENVIRONMENT HERE',
});
```

## Why `@sentry/node-core` instead of `@sentry/node`?

`@sentry/node` ships with automatic OpenTelemetry integration. This has two main downsides for us:

- PrairieLearn applications have their own OpenTelemetry setup, which conflicts with Sentry's desire to control OpenTelemetry. In isolation, this wouldn't be a problem, as they offer configuration options to disable automatic OpenTelemetry setup. However...
- It pins OpenTelemetry instrumentation packages to specific versions. This makes it hard for us to upgrade OpenTelemetry instrumentation packages independently.
- It includes a lot of unnecessary OpenTelemetry instrumentation packages that are unused in our codebase.

By using `@sentry/node-core` instead, we retain full control over the OpenTelemetry setup and the versions of OpenTelemetry instrumentation packages we use.

See <https://github.com/getsentry/sentry-javascript/issues/15213> for slightly more historical context.
npm package: @prairielearn/session
Description: The implementation borrows heavily from prior art such as [`express-session`](https://github.com/expressjs/session) and [`fastify-session`](https://github.com/fastify/session). However, the semantics and functionality have been changed to better suit Prai
Latest version: 4.0.2

# `@prairielearn/session`

The implementation borrows heavily from prior art such as [`express-session`](https://github.com/expressjs/session) and [`fastify-session`](https://github.com/fastify/session). However, the semantics and functionality have been changed to better suit PrairieLearn's needs. Specifically:

- We need to have more precise control over when the session is written back to the session store. `express-session` will try to write the session on every request, which produces an undesirable amount of load on the database.
- We need to have more precise control over when new/updated cookies are sent back to the client. In the near future, we'll need to avoid writing these cookies when requests are served from subdomains.

## Usage

```ts
import express from 'express';
import { createSessionMiddleware, MemoryStore } from '@prairielearn/session';

const app = express();

app.use(
  createSessionMiddleware({
    store: new MemoryStore(),
    secret: 'top_secret',
  }),
);
```

### Rotate session cookies

It can be useful to rotate to a new session cookie name. For instance, this can be used to provide an explicit subdomain when none was set before.

To do this, you can use a combination of `cookie.writeNames` and `cookie.writeOverrides`:

```ts
createSessionMiddleware({
  // ...
  cookie: {
    name: 'legacy_session',
    writeNames: ['legacy_session', 'session'],
    writeOverrides: [{ domain: undefined }, { domain: '.example.com' }],
  },
});
```

In this example, the session will be loaded from and persisted to the `legacy_session` cookie. However, when the session is persisted, it will also be written to a new cookie named `session`. The `domain` attribute of the `legacy_session` cookie will not be set, while the `domain` attribute of the `session` cookie will be set to `.example.com`.

After this code has been running in production for a while, it will be safe to switch to reading from and writing to the new `session` cookie exclusively:

```ts
createSessionMiddleware({
  cookie: {
    name: 'session',
    domain: '.example.com',
  },
});
```
npm package: @prairielearn/signed-token
Description: A package for generating signed tokens. Useful for CSRF tokens or generally to round-trip trusted data through an untrusted client.
Latest version: 3.1.3

# `@prairielearn/signed-token`

A package for generating signed tokens. Useful for CSRF tokens or generally to round-trip trusted data through an untrusted client.

## Usage

```ts
import {
  generateSignedToken,
  getCheckedSignedTokenData,
  checkSignedToken,
} from '@prairielearn/signed-token';

const token = generateSignedToken({ foo: 'bar' }, 'SECRET_KEY');

console.log(getCheckedSignedTokenData(token, 'SECRET_KEY', { maxAge: 60 * 1000 }));
// { foo: 'bar' }

console.log(checkSignedToken(token, { foo: 'bar' }, 'SECRET_KEY', { maxAge: 60 * 1000 }));
// true

console.log(checkSignedToken(token, { foo: 'baz' }, 'SECRET_KEY', { maxAge: 60 * 1000 }));
// false
```
npm package: @prairielearn/utils
Description: Various shared utilities.
Latest version: 3.1.2

# `@prairielearn/utils`

Various shared utilities.

## Usage

### `withResolvers()`

A tiny utility for creating Promises with exposed `resolve` and `reject` methods, similar to [`Promise.withResolvers()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers).

Once `Promise.withResolvers()` is widely supported in browsers, users of this package should switch to it.

```ts
import { withResolvers } from '@prairielearn/utils';

const { promise, resolve, reject } = withResolvers<number>();

setTimeout(() => resolve(42), 100);

promise.then((value) => {
  console.log(value); // 42
});
```
npm package: @prairielearn/vite-plugin-express
Description: A fork of [axe-me/vite-plugin-node](https://github.com/axe-me/vite-plugin-node) with support for auto-reloading the server on file changes, and removal of support code for other server types.
Latest version: 2.0.2

# `@prairielearn/vite-plugin-express`

A fork of [axe-me/vite-plugin-node](https://github.com/axe-me/vite-plugin-node) with support for auto-reloading the server on file changes, and removal of support code for other server types.
npm package: @prairielearn/zod
Description: Useful Zod schemas.
Latest version: 2.0.2

# `@prairielearn/zod`

Useful Zod schemas.

## Usage

### `BooleanFromCheckboxSchema`

```ts
import { BooleanFromCheckboxSchema } from '@prairielearn/zod';

BooleanFromCheckboxSchema.parse(''); // false
BooleanFromCheckboxSchema.parse('true'); // true
BooleanFromCheckboxSchema.parse('1'); // true
BooleanFromCheckboxSchema.parse('on'); // true
```

### `DatetimeLocalStringSchema`

```ts
import { DatetimeLocalStringSchema } from '@prairielearn/zod';

DatetimeLocalStringSchema.parse('2024-01-15T14:30'); // '2024-01-15T14:30:00'
DatetimeLocalStringSchema.parse('2024-01-15T14:30:45'); // '2024-01-15T14:30:45'
```

### `UniqueUidsFromStringSchema`

```ts
import { UniqueUidsFromStringSchema } from '@prairielearn/zod';

const schema = UniqueUidsFromStringSchema();
schema.parse('user1@example.com, user2@example.com'); // ['user1@example.com', 'user2@example.com']
schema.parse('user@example.com user@example.com'); // ['user@example.com'] (deduplicated)
```