All files / src/core/services DurableObjectService.ts

30% Statements 3/10
0% Branches 0/3
0% Functions 0/3
30% Lines 3/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 8028x                         28x                                         28x                                                                                          
import { DurableObjectBinding } from '@/core/bindings/DurableObjectBinding'
 
import type { CoordinationStore } from '@bayudwiyansatria/core'
import type { CloudflareEnv } from '@/types/CloudflareEnv'
 
/**
 * Durable Object namespace binding.
 *
 * Every setting it needs — binding name and location hint — arrives resolved
 * from the configuration layer, so nothing about them is decided here. The
 * binding itself is read from `env` per call, which is what makes one
 * module-scope instance safe to share.
 */
const objects = new DurableObjectBinding()
 
/**
 * Coordination capability, backed by Durable Objects.
 *
 * @remarks
 * A name maps to exactly one object, so concurrent requests for the same name
 * queue behind each other instead of racing — that is the whole point of
 * reaching for this rather than KV or D1.
 *
 * Requests reach an object through its stub's `fetch`, so the URLs below are
 * addressed to the object, not to the internet. The object class itself must
 * be exported from the Worker entry and declared as a migration in
 * `wrangler.json`.
 *
 * @class
 *
 * @author Bayu Dwiyan Satria
 * @version 1.0.0
 * @since 1.0.0
 */
export class DurableObjectService<E extends CloudflareEnv = CloudflareEnv> implements CoordinationStore<E> {
  /**
   * Calls the object that owns a name and parses its JSON reply.
   *
   * @param env The Worker environment.
   * @param name The name identifying the object — a room, a tenant, a session.
   * @param path Path on the object, e.g. `/increment`.
   * @param init Request options, such as `{ method: 'POST' }`.
   * @returns The object's parsed response.
   * @throws Error When the object replies with a non-OK status.
   */
  public async call<T = unknown>(env: E, name: string, path: string, init: RequestInit = {}): Promise<T> {
    const stub = objects.stubByName(env, name)
    const response = await stub.fetch(`https://durable-object${path}`, init)
 
    if (!response.ok) {
      throw new Error(`Durable Object '${name}' replied ${response.status} to ${path}`)
    }
 
    return (await response.json()) as T
  }
 
  /**
   * Creates a fresh object and returns the id needed to reach it again.
   *
   * Use this when the caller — not a name — owns the identity: a new session,
   * a new game, a new upload.
   *
   * @param env The Worker environment.
   * @returns The new object's id, as a string.
   */
  public create(env: E): string {
    return objects.stubForNewId(env).id.toString()
  }
 
  /**
   * Reports whether the namespace is bound to this Worker.
   *
   * @param env The Worker environment.
   * @returns `true` when the binding is present.
   */
  public isAvailable(env: E): boolean {
    return objects.isBound(env)
  }
}