1.0.8 • Published 5 months ago

@quisido/worker v1.0.8

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

quisi.do workers

This module contains utility classes for generating Cloudflare workers.

Exported handlers

Cloudflare expects your entrypoint to export an ExportedHandler interface. To simplify the process of binding your application code to the runtime environment, use the ExportedHandler class and pass your event handlers to its constructor.

import { ExportedHandler } from '@quisido/worker';
import MyFetchHandler from './my-fetch-handler.js';

export default new ExportedHandler({
  FetchHandler: MyFetchHandler,
});

In addition to binding event handlers to the exported handler, Handlers allow you to conveniently emit errors, logs, and metrics from anywhere in your application by binding an event listener at your entrypoint.

import { ExportedHandler, type Handler } from '@quisido/worker';
import MyFetchHandler from './my-fetch-handler.js';

export default new ExportedHandler({
  FetchHandler: MyFetchHandler,

  // Write errors to the console.
  onError(this: Handler, err: Error): void {
    this.console.error(err);
  },

  // Write logs to the console.
  onLog(this: Handler, ...messages: readonly string[]): void {
    this.console.log(...messages);
  },

  // Write metrics to the `MY_DATASET` Analytics Engine dataset.
  onMetric(this: Handler, name: string, dimensions: MetricDimensions): void {
    this.writeMetricDataPoint('MY_DATASET', name, dimensions);
  },
});

Event handlers

Handlers are class representations of Cloudfare worker event handlers (email, fetch, queue, scheduled, tail, and trace).

Currently, this package only supports the fetch event handler while developer feedback is gathered.

Cloudflare's native exported handler executes event handlers as simple functions, which lack state and limit testability. This package's Handlers are runnable interfaces that allow you to append your own properties and methods to each event handler. They come with built-in utility methods for common Cloudflare use cases.

import { type Handler, type MetricDimensions } from '@quisido/worker';
import ExampleThing from './example-thing.js';

export default async function myMethod(this: Handler): Promise<void> {
  // Emit asynchronous side effects to await after the `Response` has returned.
  this.affect(promise);

  // Emit a metric for monitoring.
  this.emitMetric('my-metric-name', {
    myBoolean: true,
    myNumber: 1234,
    myString: 'my-value',
  });

  // Fetch a Response.
  const response: Response = await this.fetch('https://...');

  // Fetch JSON.
  const responseJson: unknown = await this.fetchJson('https://...');

  // Fetch a string.
  const responseText: string = await this.fetchText('https://...');

  // Get an Analytics Engine dataset binding.
  const dataset: AnalyticsEngineDataset =
    this.getAnalyticsEngineDataset('MY_DATASET');

  // Get a D1 database binding.
  const database: D1Database = this.getD1Database('MY_DATABASE');

  /**
   * Run a D1 query.
   * ┌─────────────┬──────────┐
   * │     Key     │   Type   │
   * ├─────────────┼──────────┤
   * │ changedDb   │ boolean  │
   * │ changes     │ number   │
   * │ duration    │ number   │
   * │ lastRowId   │ number   │
   * │ rowsRead    │ number   │
   * │ rowsWritten │ number   │
   * │ sizeAfter   │ number   │
   * └─────────────┴──────────┘
   */
  const d1Response = await this.getD1Response(
    'MY_DATABASE',
    'INSERT INTO `myTable` (`myNumber`, `myString`) VALUES (?, ?)',
    [1234, 'my string'],
  );

  /**
   * Get D1 results.
   * ┌─────────────┬────────────────────────────┐
   * │     Key     │            Type            │
   * ├─────────────┼────────────────────────────┤
   * │ changedDb   │ boolean                    │
   * │ changes     │ number                     │
   * │ duration    │ number                     │
   * │ lastRowId   │ number                     │
   * │ results     │ Record<string, unknown>[]  │
   * │ rowsRead    │ number                     │
   * │ rowsWritten │ number                     │
   * │ sizeAfter   │ number                     │
   * └─────────────┴────────────────────────────┘
   */
  const { results } = await this.getD1Results(
    'MY_DATABASE',
    'SELECT `myString` FROM `myTable` WHERE `myNumber` = ?',
    [1234],
  );

  // Get a KV namespace binding.
  const namespace: KVNamespace = this.getKVNamespace('MY_NAMESPACE');

  // Get text from a KV namespace.
  const kvText: string = this.getKVNamespaceText('MY_NAMESPACE', 'KEY');

  // Get an R2 bucket binding.
  const bucket: R2Bucket = this.getR2Bucket('MY_BUCKET');

  // Log a message.
  this.log('Hello world!');

  // Emit an error.
  this.logError(new Error('Goodbye world.'));

  // Get the current timestamp (mockable).
  const startTime: number = this.now();

  // Write to a KV namespace.
  await this.putKVNamespace('MY_NAMESPACE', 'key', 'value');

  // Write to an R2 bucket.
  await this.putR2Bucket('MY_BUCKET', 'key', 'value');

  // Validate a binding.
  const myThing: Thing = this.validateBinding(
    'MY_BINDING',
    (value: unknown): value is ExampleThing =>
      value instanceof ExampleThing,
  );

  // Write a metric to an Analytics Engine dataset.
  this.writeMetricDataPoint(
    'MY_DATASET',
    'my-metric-name',
    { my: 'dimensions' },
  );
}

Fetch handlers

FetchHandler extends the Handler class. Its constructor takes a native Cloudflare fetch handler and an optional error handler. A FetchHandler includes fetch-specific properties and methods in addition to those provided by the Handler class.

import { FetchHandler } from '@quisido/worker';

export default async function myMethod(this: FetchHandler): Promise<void> {
  /**
   * `FetchHandler`-specific properties of `this`
   * ┌──────────────────────┬────────────────────────┐
   * │       Property       │          Type          │
   * ├──────────────────────┼────────────────────────┤
   * │ cookies              │ Record<string, string> │
   * │ executionContext     │ ExecutionContext       │
   * │ origin               │ string | null          │
   * │ request              │ Request                │
   * │ requestHeaders       │ Headers                │
   * │ requestMethod        │ string                 │
   * │ requestPathname      │ string                 │
   * │ requestSearchParams  │ URLSearchParams        │
   * │ requestUrl           │ URL                    │
   * └──────────────────────┴────────────────────────┘
   */

  // Get a specific cookie.
  const cookie: string | undefined = this.getCookie('MyCookie');

  // Get a search parameter.
  const searchValue: string | null = this.getRequestSearchParam('code');

  // Get the request body.
  const text: string = await this.getRequestText();
}

Metrics

The Handler utility methods will automatically emit valuable metrics for common operations. You can access the human-readable enum by importing MetricName.

Metric nameCondition
MetricName.D1PreparedStatementAllEmits after calling getD1Results
MetricName.D1PreparedStatementAllErrorEmits when getD1Results fails
MetricName.D1PreparedStatementRunEmits after calling getD1Response
MetricName.D1PreparedStatementRunErrorEmits when getD1Response fails
MetricName.FetchEmits after calling fetch
MetricName.InvalidBindingEmits when validateBinding fails
MetricName.KVNamespaceGetEmits after calling getKVNamespaceText
MetricName.KVNamespaceGetErrorEmits when getKVNamespaceText fails
MetricName.KVNamespacePutEmits after calling putKVNamespace
MetricName.KVNamespacePutErrorEmits when putKVNamespace fails
MetricName.R2BucketPutEmits after calling putR2Bucket
MetricName.R2BucketPutErrorEmits when putR2Bucket fails

Dimensions

When each metric is emit, it includes a set of dimensions as defined below.

Metric nameDimensionType
MetricName.D1PreparedStatementAllchangedDbboolean
changesnumber
durationnumber
endTimenumber
envstring
lastRowIdnumber
querystring
resultsnumber
rowsReadnumber
rowsWrittennumber
sizeAfternumber
startTimenumber
MetricName.D1PreparedStatementAllErrorendTimenumber
envstring
querystring
startTimenumber
MetricName.D1PreparedStatementRunchangedDbboolean
changesnumber
durationnumber
endTimenumber
envstring
lastRowIdnumber
querystring
rowsReadnumber
rowsWrittennumber
sizeAfternumber
startTimenumber
MetricName.D1PreparedStatementRunErrorendTimenumber
envstring
querystring
startTimenumber
MetricName.FetchendTimenumber
startTimenumber
urlstring
MetricName.InvalidBindingkeystring
typestring
valuestring
MetricName.KVNamespaceGetendTimenumber
keystring
namespacestring
startTimenumber
MetricName.KVNamespaceGetErrorendTimenumber
envstring
startTimenumber
MetricName.KVNamespacePutendTimenumber
envstring
startTimenumber
MetricName.KVNamespacePutErrorendTimenumber
envstring
startTimenumber
MetricName.R2BucketPutendTimenumber
envstring
startTimenumber
MetricName.R2BucketPutErrorendTimenumber
envstring
startTimenumber

Testing

The ExportedHandler and Handler classes were built with testing in mind. Their APIs are fully extensible and configurable across any testing environment. If you are testing with vitest, you can use the @quisido/worker-test package for a collection of common testing patterns.

1.0.8

5 months ago

1.0.7

10 months ago

1.0.6

10 months ago

1.0.5

10 months ago

1.0.3

12 months ago

1.0.2

1 year ago