ENGLISH·COURSE API
    Preparing search index...

    Driven adapter: a BlobStore that fans out over several stores.

    This is the layer that makes "the asset moved" a configuration change. It decides WHICH store answers for a key and WHAT path that key has inside it; the drivers below it know only how to resolve a path within one store.

    It deliberately does not widen the BlobStore interface. In particular RoutingBlobStore.url stays synchronous, so nothing propagates into the lesson, resource and notes adapters, into entity construction, or into the domain. Private stores are reached through CONTENT_SIGNING_ENDPOINT, which is what buys that: signing is asynchronous, a redirect is not.

    const store = new RoutingBlobStore({
    manifest: loadContentLocations("content-locations.json") ?? singleLocalStoreManifest(),
    drivers: { local: new LocalFilesystemBlobStore({ baseUrl, localRoot }) },
    });
    store.url("course/module/lesson/video.mp4");

    Implements

    Index

    Constructors

    • Parameters

      • params: {
            manifest: {
                version: 1;
                stores: Record<
                    string,
                    | { driver: "local"; baseUrl: string; pathPrefix: string }
                    | {
                        driver: "s3";
                        bucket: string;
                        region: string;
                        endpoint?: string;
                        forcePathStyle: boolean;
                        pathPrefix: string;
                        visibility: "public" | "signed";
                        publicUrl?: string;
                        signedUrlTtlSeconds?: number;
                    }
                    | {
                        driver: "gcs";
                        bucket: string;
                        apiEndpoint?: string;
                        pathPrefix: string;
                        visibility: "public"
                        | "signed";
                        publicUrl?: string;
                        signedUrlTtlSeconds?: number;
                    },
                >;
                default: string;
                routes: { prefix: string; store: string }[];
                overrides: Record<string, string>;
                assets: Record<string, { store?: string; objectPath?: string }>;
            };
            drivers: Record<string, BlobStore>;
        }
        • manifest: {
              version: 1;
              stores: Record<
                  string,
                  | { driver: "local"; baseUrl: string; pathPrefix: string }
                  | {
                      driver: "s3";
                      bucket: string;
                      region: string;
                      endpoint?: string;
                      forcePathStyle: boolean;
                      pathPrefix: string;
                      visibility: "public" | "signed";
                      publicUrl?: string;
                      signedUrlTtlSeconds?: number;
                  }
                  | {
                      driver: "gcs";
                      bucket: string;
                      apiEndpoint?: string;
                      pathPrefix: string;
                      visibility: "public"
                      | "signed";
                      publicUrl?: string;
                      signedUrlTtlSeconds?: number;
                  },
              >;
              default: string;
              routes: { prefix: string; store: string }[];
              overrides: Record<string, string>;
              assets: Record<string, { store?: string; objectPath?: string }>;
          }

          The parsed location manifest

          • version: 1
          • stores: Record<
                string,
                | { driver: "local"; baseUrl: string; pathPrefix: string }
                | {
                    driver: "s3";
                    bucket: string;
                    region: string;
                    endpoint?: string;
                    forcePathStyle: boolean;
                    pathPrefix: string;
                    visibility: "public" | "signed";
                    publicUrl?: string;
                    signedUrlTtlSeconds?: number;
                }
                | {
                    driver: "gcs";
                    bucket: string;
                    apiEndpoint?: string;
                    pathPrefix: string;
                    visibility: "public"
                    | "signed";
                    publicUrl?: string;
                    signedUrlTtlSeconds?: number;
                },
            >
          • default: string

            Store that answers for a key matching no override and no route.

          • routes: { prefix: string; store: string }[]
          • overrides: Record<string, string>

            Exact key → store name, for single assets that escape their prefix.

          • assets: Record<string, { store?: string; objectPath?: string }>

            Exact key → declared placement. Outranks every other layer.

        • drivers: Record<string, BlobStore>

          One driver per store name the manifest declares

      Returns RoutingBlobStore

      if any declared store has no driver — a gap that would otherwise surface as a runtime failure on whichever page happened to need it first

    Methods

    • Reads a bounded UTF-8 text blob for a known text key. Implementations MUST validate that the key is safe (no absolute prefixes, no .. path segments) and reject keys that point to binary asset types before returning any text. Missing or non-text keys reject; the read is not a general arbitrary-path API.

      Parameters

      • key: string

      Returns Promise<string>

    • A time-limited URL granting read access to a private store's object.

      Parameters

      • key: string

        The content key

      Returns Promise<string>

      A signed URL valid for the store's TTL

      Asynchronous, and deliberately NOT part of BlobStore. Only the signing endpoint calls it, and only it can afford to await; keeping it off the synchronous interface is what stops async from spreading into entity construction.

      if the key is unsafe, its store is public, or its driver cannot sign