@studiolayer/client 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,9 @@ import { createClient } from '@studiolayer/client'
25
25
 
26
26
  const studio = createClient({
27
27
  apiKey: process.env.STUDIOLAYER_API_KEY!, // slk_...
28
- baseUrl: 'https://studio.example.com', // your StudioLayer server
28
+ // baseUrl defaults to https://app.studiolayer.io;
29
+ // set it only for a self-hosted server:
30
+ // baseUrl: 'https://studio.example.com',
29
31
  })
30
32
 
31
33
  // Discover what this key can reach.
package/dist/index.cjs CHANGED
@@ -85,12 +85,12 @@ var StudioLayerError = class _StudioLayerError extends Error {
85
85
  };
86
86
 
87
87
  // src/client.ts
88
+ var DEFAULT_BASE_URL = "https://app.studiolayer.io";
88
89
  var StudioLayerClient = class {
89
90
  constructor(options) {
90
91
  if (!options.apiKey) throw new Error("StudioLayerClient: `apiKey` is required");
91
- if (!options.baseUrl) throw new Error("StudioLayerClient: `baseUrl` is required");
92
92
  this.apiKey = options.apiKey;
93
- this.baseUrl = options.baseUrl.replace(/\/+$/, "");
93
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
94
94
  const resolvedFetch = options.fetch ?? globalThis.fetch;
95
95
  if (!resolvedFetch) {
96
96
  throw new Error("StudioLayerClient: no `fetch` available; pass one via options.fetch (Node < 18)");
@@ -277,6 +277,7 @@ function createClient(options) {
277
277
  }
278
278
 
279
279
  exports.ContentCache = ContentCache;
280
+ exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
280
281
  exports.DatasetHandle = DatasetHandle;
281
282
  exports.MemoryCacheStore = MemoryCacheStore;
282
283
  exports.StudioLayerClient = StudioLayerClient;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cache.ts","../src/errors.ts","../src/client.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Read caching for the content client. GET responses (schema, record lists,\n * single records, queries) are cached by request key; writes invalidate the\n * affected keys automatically. Swap in a custom `CacheStore` to share a cache\n * across client instances or to back it with something persistent.\n */\n\nexport interface CacheEntry {\n value: unknown\n /** Epoch ms when the entry expires. `0` means it never expires. */\n expiresAt: number\n}\n\n/** Pluggable backing store. The default is an in-memory, insertion-ordered map. */\nexport interface CacheStore {\n get(key: string): CacheEntry | undefined\n set(key: string, entry: CacheEntry): void\n delete(key: string): void\n /** Iterate current keys (used for prefix invalidation). */\n keys(): Iterable<string>\n clear(): void\n}\n\nexport interface CacheOptions {\n /** Master switch. Default `true`. */\n enabled?: boolean\n /** Time-to-live for cached reads, in ms. Default `60000`. `0` = never expires. */\n ttl?: number\n /** Soft cap on entries; oldest are evicted first. Default `500`. */\n maxEntries?: number\n /** Custom backing store. Defaults to an in-memory store. */\n store?: CacheStore\n}\n\n/** In-memory store with insertion-order (FIFO) eviction once `maxEntries` is hit. */\nexport class MemoryCacheStore implements CacheStore {\n private map = new Map<string, CacheEntry>()\n\n constructor(private maxEntries = 500) {}\n\n get(key: string): CacheEntry | undefined {\n return this.map.get(key)\n }\n\n set(key: string, entry: CacheEntry): void {\n // Re-insert so recently written keys are considered newest.\n this.map.delete(key)\n this.map.set(key, entry)\n while (this.map.size > this.maxEntries) {\n const oldest = this.map.keys().next().value\n if (oldest === undefined) break\n this.map.delete(oldest)\n }\n }\n\n delete(key: string): void {\n this.map.delete(key)\n }\n\n keys(): Iterable<string> {\n return this.map.keys()\n }\n\n clear(): void {\n this.map.clear()\n }\n}\n\n/**\n * Thin cache facade the client talks to. Owns TTL logic and prefix-based\n * invalidation so the client only deals in cache keys.\n */\nexport class ContentCache {\n readonly enabled: boolean\n private ttl: number\n private store: CacheStore\n\n constructor(opts: CacheOptions = {}) {\n this.enabled = opts.enabled ?? true\n this.ttl = opts.ttl ?? 60_000\n this.store = opts.store ?? new MemoryCacheStore(opts.maxEntries ?? 500)\n }\n\n /** Return a fresh cached value for `key`, or `undefined` on miss/expiry. */\n get<T>(key: string, now: number): T | undefined {\n if (!this.enabled) return undefined\n const entry = this.store.get(key)\n if (!entry) return undefined\n if (entry.expiresAt !== 0 && entry.expiresAt <= now) {\n this.store.delete(key)\n return undefined\n }\n return entry.value as T\n }\n\n set(key: string, value: unknown, now: number): void {\n if (!this.enabled) return\n this.store.set(key, { value, expiresAt: this.ttl === 0 ? 0 : now + this.ttl })\n }\n\n /** Drop one exact key. */\n delete(key: string): void {\n this.store.delete(key)\n }\n\n /** Drop every key that starts with `prefix`. */\n deletePrefix(prefix: string): void {\n for (const key of [...this.store.keys()]) {\n if (key.startsWith(prefix)) this.store.delete(key)\n }\n }\n\n clear(): void {\n this.store.clear()\n }\n}\n","/**\n * Error thrown for any non-2xx response from the content API. `status` carries\n * the HTTP status code so callers can branch (401 bad key, 403 out of scope,\n * 404 missing node/dataset/record, 422 query failed).\n */\nexport class StudioLayerError extends Error {\n readonly status: number\n /** The raw parsed response body, when the server returned one. */\n readonly body: unknown\n\n constructor(message: string, status: number, body?: unknown) {\n super(message)\n this.name = 'StudioLayerError'\n this.status = status\n this.body = body\n // Restore prototype chain for `instanceof` when transpiled to ES5.\n Object.setPrototypeOf(this, StudioLayerError.prototype)\n }\n\n get isUnauthorized(): boolean {\n return this.status === 401\n }\n\n get isForbidden(): boolean {\n return this.status === 403\n }\n\n get isNotFound(): boolean {\n return this.status === 404\n }\n}\n","import { ContentCache, type CacheOptions } from './cache'\nimport { StudioLayerError } from './errors'\nimport type { ContentRecord, ContentSchema, QueryResult, SchemaNode } from './types'\n\nexport interface StudioLayerClientOptions {\n /**\n * Project API key, `slk_...`. Mint one in the project's settings under\n * \"API keys\". Its reach is the union of its per-node read/write scopes.\n */\n apiKey: string\n /**\n * Base URL of your StudioLayer server, without a trailing `/api/content`\n * (that path is appended automatically), e.g. `https://studio.example.com`.\n */\n baseUrl: string\n /** Read caching. Pass `false` to disable, or an options object to tune it. */\n cache?: boolean | CacheOptions\n /** Custom `fetch` implementation. Defaults to the global `fetch`. */\n fetch?: typeof fetch\n /** Extra headers merged into every request. */\n headers?: Record<string, string>\n}\n\n/** Per-call read options. */\nexport interface ReadOptions {\n /** Set `false` to bypass the cache for this call and always hit the network. */\n cache?: boolean\n}\n\ntype Method = 'GET' | 'POST' | 'PATCH'\n\n/**\n * Typed client for the StudioLayer content API: read and write a project's node\n * datasets from any website or app. Reads are cached (see the `cache` option);\n * writes invalidate the affected cache entries automatically.\n *\n * ```ts\n * const studio = new StudioLayerClient({ apiKey: 'slk_...', baseUrl: 'https://studio.example.com' })\n * const posts = await studio.dataset<Post>('blog', 'posts').list()\n * ```\n */\nexport class StudioLayerClient {\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly fetchImpl: typeof fetch\n private readonly headers: Record<string, string>\n private readonly cache: ContentCache\n\n constructor(options: StudioLayerClientOptions) {\n if (!options.apiKey) throw new Error('StudioLayerClient: `apiKey` is required')\n if (!options.baseUrl) throw new Error('StudioLayerClient: `baseUrl` is required')\n\n this.apiKey = options.apiKey\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n const resolvedFetch = options.fetch ?? globalThis.fetch\n if (!resolvedFetch) {\n throw new Error('StudioLayerClient: no `fetch` available; pass one via options.fetch (Node < 18)')\n }\n this.fetchImpl = resolvedFetch.bind(globalThis)\n this.headers = options.headers ?? {}\n\n const cacheOpt = options.cache\n this.cache = new ContentCache(\n cacheOpt === false ? { enabled: false }\n : cacheOpt === true || cacheOpt === undefined ? {}\n : cacheOpt,\n )\n }\n\n // ── Introspection ─────────────────────────────────────────────────────────\n\n /** The full content shape this key can reach: nodes, datasets, field schemas. */\n async schema(opts?: ReadOptions): Promise<ContentSchema> {\n return this.cachedGet<ContentSchema>('schema', '/schema', opts)\n }\n\n /** Shorthand for `schema()` then `.nodes`. */\n async nodes(opts?: ReadOptions): Promise<SchemaNode[]> {\n return (await this.schema(opts)).nodes\n }\n\n // ── Records ───────────────────────────────────────────────────────────────\n\n /** List every record in a dataset (references inflated). Requires read scope. */\n async listRecords<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n opts?: ReadOptions,\n ): Promise<ContentRecord<T>[]> {\n const res = await this.cachedGet<{ records: ContentRecord<T>[] }>(\n recordsKey(nodeSlug, datasetSlug),\n datasetPath(nodeSlug, datasetSlug),\n opts,\n )\n return res.records\n }\n\n /** Read one record by uid. Requires read scope. Throws 404 if it does not exist. */\n async getRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n recordUid: string,\n opts?: ReadOptions,\n ): Promise<ContentRecord<T>> {\n const res = await this.cachedGet<{ record: ContentRecord<T> }>(\n recordKey(nodeSlug, datasetSlug, recordUid),\n `${datasetPath(nodeSlug, datasetSlug)}/${encodeURIComponent(recordUid)}`,\n opts,\n )\n return res.record\n }\n\n /** Create a record. Requires write scope. Invalidates cached reads of the dataset. */\n async createRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n data: Record<string, unknown>,\n ): Promise<ContentRecord<T>> {\n const res = await this.request<{ record: ContentRecord<T> }>(\n 'POST',\n datasetPath(nodeSlug, datasetSlug),\n { data },\n )\n this.invalidateDataset(nodeSlug, datasetSlug)\n return res.record\n }\n\n /**\n * Partially update a record. The patch is merged onto the stored data (omitted\n * top-level fields are preserved). Requires write scope. Invalidates cached\n * reads of the dataset and of this record.\n */\n async updateRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n recordUid: string,\n data: Record<string, unknown>,\n ): Promise<ContentRecord<T>> {\n const res = await this.request<{ record: ContentRecord<T> }>(\n 'PATCH',\n `${datasetPath(nodeSlug, datasetSlug)}/${encodeURIComponent(recordUid)}`,\n { data },\n )\n this.invalidateDataset(nodeSlug, datasetSlug)\n this.cache.delete(recordKey(nodeSlug, datasetSlug, recordUid))\n return res.record\n }\n\n // -- Queries --\n\n /** Run a saved query (`queries.<slug>`) and return its result. Requires read scope. */\n async query<V = unknown>(querySlug: string, opts?: ReadOptions): Promise<QueryResult<V>> {\n return this.cachedGet<QueryResult<V>>(\n `query:${querySlug}`,\n `/queries/${encodeURIComponent(querySlug)}`,\n opts,\n )\n }\n\n // ── Fluent dataset handle ───────────────────────────────────────────────────\n\n /**\n * A fluent, type-parameterised handle to one dataset:\n * `client.dataset<Post>('blog', 'posts').list()`.\n */\n dataset<T = Record<string, unknown>>(nodeSlug: string, datasetSlug: string): DatasetHandle<T> {\n return new DatasetHandle<T>(this, nodeSlug, datasetSlug)\n }\n\n // ── Cache control ───────────────────────────────────────────────────────────\n\n /**\n * Clear cached reads. With no argument, clears everything. Pass a scope to\n * clear just part of it: a node slug, or `{ node, dataset }` for one dataset.\n */\n clearCache(scope?: string | { node: string, dataset?: string }): void {\n if (scope === undefined) {\n this.cache.clear()\n return\n }\n if (typeof scope === 'string') {\n this.cache.deletePrefix(`records:${scope}/`)\n this.cache.deletePrefix(`record:${scope}/`)\n return\n }\n if (scope.dataset) {\n this.invalidateDataset(scope.node, scope.dataset)\n } else {\n this.cache.deletePrefix(`records:${scope.node}/`)\n this.cache.deletePrefix(`record:${scope.node}/`)\n }\n }\n\n /** Drop cached record reads for a dataset, plus all query results (which may aggregate it). */\n private invalidateDataset(nodeSlug: string, datasetSlug: string): void {\n this.cache.delete(recordsKey(nodeSlug, datasetSlug))\n this.cache.deletePrefix(`${recordKey(nodeSlug, datasetSlug, '')}`)\n this.cache.deletePrefix('query:')\n }\n\n // ── Transport ───────────────────────────────────────────────────────────────\n\n private async cachedGet<T>(cacheKey: string, path: string, opts?: ReadOptions): Promise<T> {\n const useCache = opts?.cache !== false\n const now = Date.now()\n if (useCache) {\n const hit = this.cache.get<T>(cacheKey, now)\n if (hit !== undefined) return hit\n }\n const value = await this.request<T>('GET', path)\n if (useCache) this.cache.set(cacheKey, value, now)\n return value\n }\n\n private async request<T>(method: Method, path: string, body?: unknown): Promise<T> {\n const res = await this.fetchImpl(`${this.baseUrl}/api/content${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...this.headers,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n })\n\n if (!res.ok) {\n let message = res.statusText || `Request failed with status ${res.status}`\n let parsed: unknown\n try {\n parsed = await res.json()\n const m = parsed as { message?: unknown, statusMessage?: unknown }\n if (typeof m?.message === 'string') message = m.message\n else if (typeof m?.statusMessage === 'string') message = m.statusMessage\n } catch {\n /* non-JSON error body; keep the status text */\n }\n throw new StudioLayerError(message, res.status, parsed)\n }\n\n if (res.status === 204) return undefined as T\n return res.json() as Promise<T>\n }\n}\n\n/** Fluent, dataset-scoped view returned by `client.dataset()`. */\nexport class DatasetHandle<T = Record<string, unknown>> {\n constructor(\n private readonly client: StudioLayerClient,\n private readonly nodeSlug: string,\n private readonly datasetSlug: string,\n ) {}\n\n list(opts?: ReadOptions): Promise<ContentRecord<T>[]> {\n return this.client.listRecords<T>(this.nodeSlug, this.datasetSlug, opts)\n }\n\n get(recordUid: string, opts?: ReadOptions): Promise<ContentRecord<T>> {\n return this.client.getRecord<T>(this.nodeSlug, this.datasetSlug, recordUid, opts)\n }\n\n create(data: Record<string, unknown>): Promise<ContentRecord<T>> {\n return this.client.createRecord<T>(this.nodeSlug, this.datasetSlug, data)\n }\n\n update(recordUid: string, data: Record<string, unknown>): Promise<ContentRecord<T>> {\n return this.client.updateRecord<T>(this.nodeSlug, this.datasetSlug, recordUid, data)\n }\n\n /** Clear cached reads for this dataset. */\n clearCache(): void {\n this.client.clearCache({ node: this.nodeSlug, dataset: this.datasetSlug })\n }\n}\n\n// ── Cache-key + path helpers ──────────────────────────────────────────────────\n\nfunction datasetPath(nodeSlug: string, datasetSlug: string): string {\n return `/nodes/${encodeURIComponent(nodeSlug)}/datasets/${encodeURIComponent(datasetSlug)}/records`\n}\n\nfunction recordsKey(nodeSlug: string, datasetSlug: string): string {\n return `records:${nodeSlug}/${datasetSlug}`\n}\n\nfunction recordKey(nodeSlug: string, datasetSlug: string, recordUid: string): string {\n return `record:${nodeSlug}/${datasetSlug}/${recordUid}`\n}\n","export { StudioLayerClient, DatasetHandle } from './client'\nexport type { StudioLayerClientOptions, ReadOptions } from './client'\nexport { StudioLayerError } from './errors'\nexport { ContentCache, MemoryCacheStore } from './cache'\nexport type { CacheOptions, CacheStore, CacheEntry } from './cache'\nexport type {\n ContentSchema,\n SchemaNode,\n SchemaDataset,\n SchemaField,\n FieldKind,\n FieldType,\n ContentRecord,\n QueryResult,\n QueryShape,\n} from './types'\n\nimport { StudioLayerClient, type StudioLayerClientOptions } from './client'\n\n/** Convenience factory: `createClient({ apiKey, baseUrl })`. */\nexport function createClient(options: StudioLayerClientOptions): StudioLayerClient {\n return new StudioLayerClient(options)\n}\n"]}
1
+ {"version":3,"sources":["../src/cache.ts","../src/errors.ts","../src/client.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Read caching for the content client. GET responses (schema, record lists,\n * single records, queries) are cached by request key; writes invalidate the\n * affected keys automatically. Swap in a custom `CacheStore` to share a cache\n * across client instances or to back it with something persistent.\n */\n\nexport interface CacheEntry {\n value: unknown\n /** Epoch ms when the entry expires. `0` means it never expires. */\n expiresAt: number\n}\n\n/** Pluggable backing store. The default is an in-memory, insertion-ordered map. */\nexport interface CacheStore {\n get(key: string): CacheEntry | undefined\n set(key: string, entry: CacheEntry): void\n delete(key: string): void\n /** Iterate current keys (used for prefix invalidation). */\n keys(): Iterable<string>\n clear(): void\n}\n\nexport interface CacheOptions {\n /** Master switch. Default `true`. */\n enabled?: boolean\n /** Time-to-live for cached reads, in ms. Default `60000`. `0` = never expires. */\n ttl?: number\n /** Soft cap on entries; oldest are evicted first. Default `500`. */\n maxEntries?: number\n /** Custom backing store. Defaults to an in-memory store. */\n store?: CacheStore\n}\n\n/** In-memory store with insertion-order (FIFO) eviction once `maxEntries` is hit. */\nexport class MemoryCacheStore implements CacheStore {\n private map = new Map<string, CacheEntry>()\n\n constructor(private maxEntries = 500) {}\n\n get(key: string): CacheEntry | undefined {\n return this.map.get(key)\n }\n\n set(key: string, entry: CacheEntry): void {\n // Re-insert so recently written keys are considered newest.\n this.map.delete(key)\n this.map.set(key, entry)\n while (this.map.size > this.maxEntries) {\n const oldest = this.map.keys().next().value\n if (oldest === undefined) break\n this.map.delete(oldest)\n }\n }\n\n delete(key: string): void {\n this.map.delete(key)\n }\n\n keys(): Iterable<string> {\n return this.map.keys()\n }\n\n clear(): void {\n this.map.clear()\n }\n}\n\n/**\n * Thin cache facade the client talks to. Owns TTL logic and prefix-based\n * invalidation so the client only deals in cache keys.\n */\nexport class ContentCache {\n readonly enabled: boolean\n private ttl: number\n private store: CacheStore\n\n constructor(opts: CacheOptions = {}) {\n this.enabled = opts.enabled ?? true\n this.ttl = opts.ttl ?? 60_000\n this.store = opts.store ?? new MemoryCacheStore(opts.maxEntries ?? 500)\n }\n\n /** Return a fresh cached value for `key`, or `undefined` on miss/expiry. */\n get<T>(key: string, now: number): T | undefined {\n if (!this.enabled) return undefined\n const entry = this.store.get(key)\n if (!entry) return undefined\n if (entry.expiresAt !== 0 && entry.expiresAt <= now) {\n this.store.delete(key)\n return undefined\n }\n return entry.value as T\n }\n\n set(key: string, value: unknown, now: number): void {\n if (!this.enabled) return\n this.store.set(key, { value, expiresAt: this.ttl === 0 ? 0 : now + this.ttl })\n }\n\n /** Drop one exact key. */\n delete(key: string): void {\n this.store.delete(key)\n }\n\n /** Drop every key that starts with `prefix`. */\n deletePrefix(prefix: string): void {\n for (const key of [...this.store.keys()]) {\n if (key.startsWith(prefix)) this.store.delete(key)\n }\n }\n\n clear(): void {\n this.store.clear()\n }\n}\n","/**\n * Error thrown for any non-2xx response from the content API. `status` carries\n * the HTTP status code so callers can branch (401 bad key, 403 out of scope,\n * 404 missing node/dataset/record, 422 query failed).\n */\nexport class StudioLayerError extends Error {\n readonly status: number\n /** The raw parsed response body, when the server returned one. */\n readonly body: unknown\n\n constructor(message: string, status: number, body?: unknown) {\n super(message)\n this.name = 'StudioLayerError'\n this.status = status\n this.body = body\n // Restore prototype chain for `instanceof` when transpiled to ES5.\n Object.setPrototypeOf(this, StudioLayerError.prototype)\n }\n\n get isUnauthorized(): boolean {\n return this.status === 401\n }\n\n get isForbidden(): boolean {\n return this.status === 403\n }\n\n get isNotFound(): boolean {\n return this.status === 404\n }\n}\n","import { ContentCache, type CacheOptions } from './cache'\nimport { StudioLayerError } from './errors'\nimport type { ContentRecord, ContentSchema, QueryResult, SchemaNode } from './types'\n\n/** Hosted StudioLayer platform. Used when no `baseUrl` is provided. */\nexport const DEFAULT_BASE_URL = 'https://app.studiolayer.io'\n\nexport interface StudioLayerClientOptions {\n /**\n * Project API key, `slk_...`. Mint one in the project's settings under\n * \"API keys\". Its reach is the union of its per-node read/write scopes.\n */\n apiKey: string\n /**\n * Base URL of your StudioLayer server, without a trailing `/api/content`\n * (that path is appended automatically). Defaults to the hosted platform at\n * `https://app.studiolayer.io`; override it for a self-hosted instance.\n */\n baseUrl?: string\n /** Read caching. Pass `false` to disable, or an options object to tune it. */\n cache?: boolean | CacheOptions\n /** Custom `fetch` implementation. Defaults to the global `fetch`. */\n fetch?: typeof fetch\n /** Extra headers merged into every request. */\n headers?: Record<string, string>\n}\n\n/** Per-call read options. */\nexport interface ReadOptions {\n /** Set `false` to bypass the cache for this call and always hit the network. */\n cache?: boolean\n}\n\ntype Method = 'GET' | 'POST' | 'PATCH'\n\n/**\n * Typed client for the StudioLayer content API: read and write a project's node\n * datasets from any website or app. Reads are cached (see the `cache` option);\n * writes invalidate the affected cache entries automatically.\n *\n * ```ts\n * const studio = new StudioLayerClient({ apiKey: 'slk_...', baseUrl: 'https://studio.example.com' })\n * const posts = await studio.dataset<Post>('blog', 'posts').list()\n * ```\n */\nexport class StudioLayerClient {\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly fetchImpl: typeof fetch\n private readonly headers: Record<string, string>\n private readonly cache: ContentCache\n\n constructor(options: StudioLayerClientOptions) {\n if (!options.apiKey) throw new Error('StudioLayerClient: `apiKey` is required')\n\n this.apiKey = options.apiKey\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '')\n const resolvedFetch = options.fetch ?? globalThis.fetch\n if (!resolvedFetch) {\n throw new Error('StudioLayerClient: no `fetch` available; pass one via options.fetch (Node < 18)')\n }\n this.fetchImpl = resolvedFetch.bind(globalThis)\n this.headers = options.headers ?? {}\n\n const cacheOpt = options.cache\n this.cache = new ContentCache(\n cacheOpt === false ? { enabled: false }\n : cacheOpt === true || cacheOpt === undefined ? {}\n : cacheOpt,\n )\n }\n\n // ── Introspection ─────────────────────────────────────────────────────────\n\n /** The full content shape this key can reach: nodes, datasets, field schemas. */\n async schema(opts?: ReadOptions): Promise<ContentSchema> {\n return this.cachedGet<ContentSchema>('schema', '/schema', opts)\n }\n\n /** Shorthand for `schema()` then `.nodes`. */\n async nodes(opts?: ReadOptions): Promise<SchemaNode[]> {\n return (await this.schema(opts)).nodes\n }\n\n // ── Records ───────────────────────────────────────────────────────────────\n\n /** List every record in a dataset (references inflated). Requires read scope. */\n async listRecords<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n opts?: ReadOptions,\n ): Promise<ContentRecord<T>[]> {\n const res = await this.cachedGet<{ records: ContentRecord<T>[] }>(\n recordsKey(nodeSlug, datasetSlug),\n datasetPath(nodeSlug, datasetSlug),\n opts,\n )\n return res.records\n }\n\n /** Read one record by uid. Requires read scope. Throws 404 if it does not exist. */\n async getRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n recordUid: string,\n opts?: ReadOptions,\n ): Promise<ContentRecord<T>> {\n const res = await this.cachedGet<{ record: ContentRecord<T> }>(\n recordKey(nodeSlug, datasetSlug, recordUid),\n `${datasetPath(nodeSlug, datasetSlug)}/${encodeURIComponent(recordUid)}`,\n opts,\n )\n return res.record\n }\n\n /** Create a record. Requires write scope. Invalidates cached reads of the dataset. */\n async createRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n data: Record<string, unknown>,\n ): Promise<ContentRecord<T>> {\n const res = await this.request<{ record: ContentRecord<T> }>(\n 'POST',\n datasetPath(nodeSlug, datasetSlug),\n { data },\n )\n this.invalidateDataset(nodeSlug, datasetSlug)\n return res.record\n }\n\n /**\n * Partially update a record. The patch is merged onto the stored data (omitted\n * top-level fields are preserved). Requires write scope. Invalidates cached\n * reads of the dataset and of this record.\n */\n async updateRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n recordUid: string,\n data: Record<string, unknown>,\n ): Promise<ContentRecord<T>> {\n const res = await this.request<{ record: ContentRecord<T> }>(\n 'PATCH',\n `${datasetPath(nodeSlug, datasetSlug)}/${encodeURIComponent(recordUid)}`,\n { data },\n )\n this.invalidateDataset(nodeSlug, datasetSlug)\n this.cache.delete(recordKey(nodeSlug, datasetSlug, recordUid))\n return res.record\n }\n\n // -- Queries --\n\n /** Run a saved query (`queries.<slug>`) and return its result. Requires read scope. */\n async query<V = unknown>(querySlug: string, opts?: ReadOptions): Promise<QueryResult<V>> {\n return this.cachedGet<QueryResult<V>>(\n `query:${querySlug}`,\n `/queries/${encodeURIComponent(querySlug)}`,\n opts,\n )\n }\n\n // ── Fluent dataset handle ───────────────────────────────────────────────────\n\n /**\n * A fluent, type-parameterised handle to one dataset:\n * `client.dataset<Post>('blog', 'posts').list()`.\n */\n dataset<T = Record<string, unknown>>(nodeSlug: string, datasetSlug: string): DatasetHandle<T> {\n return new DatasetHandle<T>(this, nodeSlug, datasetSlug)\n }\n\n // ── Cache control ───────────────────────────────────────────────────────────\n\n /**\n * Clear cached reads. With no argument, clears everything. Pass a scope to\n * clear just part of it: a node slug, or `{ node, dataset }` for one dataset.\n */\n clearCache(scope?: string | { node: string, dataset?: string }): void {\n if (scope === undefined) {\n this.cache.clear()\n return\n }\n if (typeof scope === 'string') {\n this.cache.deletePrefix(`records:${scope}/`)\n this.cache.deletePrefix(`record:${scope}/`)\n return\n }\n if (scope.dataset) {\n this.invalidateDataset(scope.node, scope.dataset)\n } else {\n this.cache.deletePrefix(`records:${scope.node}/`)\n this.cache.deletePrefix(`record:${scope.node}/`)\n }\n }\n\n /** Drop cached record reads for a dataset, plus all query results (which may aggregate it). */\n private invalidateDataset(nodeSlug: string, datasetSlug: string): void {\n this.cache.delete(recordsKey(nodeSlug, datasetSlug))\n this.cache.deletePrefix(`${recordKey(nodeSlug, datasetSlug, '')}`)\n this.cache.deletePrefix('query:')\n }\n\n // ── Transport ───────────────────────────────────────────────────────────────\n\n private async cachedGet<T>(cacheKey: string, path: string, opts?: ReadOptions): Promise<T> {\n const useCache = opts?.cache !== false\n const now = Date.now()\n if (useCache) {\n const hit = this.cache.get<T>(cacheKey, now)\n if (hit !== undefined) return hit\n }\n const value = await this.request<T>('GET', path)\n if (useCache) this.cache.set(cacheKey, value, now)\n return value\n }\n\n private async request<T>(method: Method, path: string, body?: unknown): Promise<T> {\n const res = await this.fetchImpl(`${this.baseUrl}/api/content${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...this.headers,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n })\n\n if (!res.ok) {\n let message = res.statusText || `Request failed with status ${res.status}`\n let parsed: unknown\n try {\n parsed = await res.json()\n const m = parsed as { message?: unknown, statusMessage?: unknown }\n if (typeof m?.message === 'string') message = m.message\n else if (typeof m?.statusMessage === 'string') message = m.statusMessage\n } catch {\n /* non-JSON error body; keep the status text */\n }\n throw new StudioLayerError(message, res.status, parsed)\n }\n\n if (res.status === 204) return undefined as T\n return res.json() as Promise<T>\n }\n}\n\n/** Fluent, dataset-scoped view returned by `client.dataset()`. */\nexport class DatasetHandle<T = Record<string, unknown>> {\n constructor(\n private readonly client: StudioLayerClient,\n private readonly nodeSlug: string,\n private readonly datasetSlug: string,\n ) {}\n\n list(opts?: ReadOptions): Promise<ContentRecord<T>[]> {\n return this.client.listRecords<T>(this.nodeSlug, this.datasetSlug, opts)\n }\n\n get(recordUid: string, opts?: ReadOptions): Promise<ContentRecord<T>> {\n return this.client.getRecord<T>(this.nodeSlug, this.datasetSlug, recordUid, opts)\n }\n\n create(data: Record<string, unknown>): Promise<ContentRecord<T>> {\n return this.client.createRecord<T>(this.nodeSlug, this.datasetSlug, data)\n }\n\n update(recordUid: string, data: Record<string, unknown>): Promise<ContentRecord<T>> {\n return this.client.updateRecord<T>(this.nodeSlug, this.datasetSlug, recordUid, data)\n }\n\n /** Clear cached reads for this dataset. */\n clearCache(): void {\n this.client.clearCache({ node: this.nodeSlug, dataset: this.datasetSlug })\n }\n}\n\n// ── Cache-key + path helpers ──────────────────────────────────────────────────\n\nfunction datasetPath(nodeSlug: string, datasetSlug: string): string {\n return `/nodes/${encodeURIComponent(nodeSlug)}/datasets/${encodeURIComponent(datasetSlug)}/records`\n}\n\nfunction recordsKey(nodeSlug: string, datasetSlug: string): string {\n return `records:${nodeSlug}/${datasetSlug}`\n}\n\nfunction recordKey(nodeSlug: string, datasetSlug: string, recordUid: string): string {\n return `record:${nodeSlug}/${datasetSlug}/${recordUid}`\n}\n","export { StudioLayerClient, DatasetHandle, DEFAULT_BASE_URL } from './client'\nexport type { StudioLayerClientOptions, ReadOptions } from './client'\nexport { StudioLayerError } from './errors'\nexport { ContentCache, MemoryCacheStore } from './cache'\nexport type { CacheOptions, CacheStore, CacheEntry } from './cache'\nexport type {\n ContentSchema,\n SchemaNode,\n SchemaDataset,\n SchemaField,\n FieldKind,\n FieldType,\n ContentRecord,\n QueryResult,\n QueryShape,\n} from './types'\n\nimport { StudioLayerClient, type StudioLayerClientOptions } from './client'\n\n/** Convenience factory: `createClient({ apiKey, baseUrl })`. */\nexport function createClient(options: StudioLayerClientOptions): StudioLayerClient {\n return new StudioLayerClient(options)\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -119,6 +119,8 @@ interface QueryResult<V = unknown> {
119
119
  value: V;
120
120
  }
121
121
 
122
+ /** Hosted StudioLayer platform. Used when no `baseUrl` is provided. */
123
+ declare const DEFAULT_BASE_URL = "https://app.studiolayer.io";
122
124
  interface StudioLayerClientOptions {
123
125
  /**
124
126
  * Project API key, `slk_...`. Mint one in the project's settings under
@@ -127,9 +129,10 @@ interface StudioLayerClientOptions {
127
129
  apiKey: string;
128
130
  /**
129
131
  * Base URL of your StudioLayer server, without a trailing `/api/content`
130
- * (that path is appended automatically), e.g. `https://studio.example.com`.
132
+ * (that path is appended automatically). Defaults to the hosted platform at
133
+ * `https://app.studiolayer.io`; override it for a self-hosted instance.
131
134
  */
132
- baseUrl: string;
135
+ baseUrl?: string;
133
136
  /** Read caching. Pass `false` to disable, or an options object to tune it. */
134
137
  cache?: boolean | CacheOptions;
135
138
  /** Custom `fetch` implementation. Defaults to the global `fetch`. */
@@ -227,4 +230,4 @@ declare class StudioLayerError extends Error {
227
230
  /** Convenience factory: `createClient({ apiKey, baseUrl })`. */
228
231
  declare function createClient(options: StudioLayerClientOptions): StudioLayerClient;
229
232
 
230
- export { type CacheEntry, type CacheOptions, type CacheStore, ContentCache, type ContentRecord, type ContentSchema, DatasetHandle, type FieldKind, type FieldType, MemoryCacheStore, type QueryResult, type QueryShape, type ReadOptions, type SchemaDataset, type SchemaField, type SchemaNode, StudioLayerClient, type StudioLayerClientOptions, StudioLayerError, createClient };
233
+ export { type CacheEntry, type CacheOptions, type CacheStore, ContentCache, type ContentRecord, type ContentSchema, DEFAULT_BASE_URL, DatasetHandle, type FieldKind, type FieldType, MemoryCacheStore, type QueryResult, type QueryShape, type ReadOptions, type SchemaDataset, type SchemaField, type SchemaNode, StudioLayerClient, type StudioLayerClientOptions, StudioLayerError, createClient };
package/dist/index.d.ts CHANGED
@@ -119,6 +119,8 @@ interface QueryResult<V = unknown> {
119
119
  value: V;
120
120
  }
121
121
 
122
+ /** Hosted StudioLayer platform. Used when no `baseUrl` is provided. */
123
+ declare const DEFAULT_BASE_URL = "https://app.studiolayer.io";
122
124
  interface StudioLayerClientOptions {
123
125
  /**
124
126
  * Project API key, `slk_...`. Mint one in the project's settings under
@@ -127,9 +129,10 @@ interface StudioLayerClientOptions {
127
129
  apiKey: string;
128
130
  /**
129
131
  * Base URL of your StudioLayer server, without a trailing `/api/content`
130
- * (that path is appended automatically), e.g. `https://studio.example.com`.
132
+ * (that path is appended automatically). Defaults to the hosted platform at
133
+ * `https://app.studiolayer.io`; override it for a self-hosted instance.
131
134
  */
132
- baseUrl: string;
135
+ baseUrl?: string;
133
136
  /** Read caching. Pass `false` to disable, or an options object to tune it. */
134
137
  cache?: boolean | CacheOptions;
135
138
  /** Custom `fetch` implementation. Defaults to the global `fetch`. */
@@ -227,4 +230,4 @@ declare class StudioLayerError extends Error {
227
230
  /** Convenience factory: `createClient({ apiKey, baseUrl })`. */
228
231
  declare function createClient(options: StudioLayerClientOptions): StudioLayerClient;
229
232
 
230
- export { type CacheEntry, type CacheOptions, type CacheStore, ContentCache, type ContentRecord, type ContentSchema, DatasetHandle, type FieldKind, type FieldType, MemoryCacheStore, type QueryResult, type QueryShape, type ReadOptions, type SchemaDataset, type SchemaField, type SchemaNode, StudioLayerClient, type StudioLayerClientOptions, StudioLayerError, createClient };
233
+ export { type CacheEntry, type CacheOptions, type CacheStore, ContentCache, type ContentRecord, type ContentSchema, DEFAULT_BASE_URL, DatasetHandle, type FieldKind, type FieldType, MemoryCacheStore, type QueryResult, type QueryShape, type ReadOptions, type SchemaDataset, type SchemaField, type SchemaNode, StudioLayerClient, type StudioLayerClientOptions, StudioLayerError, createClient };
package/dist/index.js CHANGED
@@ -83,12 +83,12 @@ var StudioLayerError = class _StudioLayerError extends Error {
83
83
  };
84
84
 
85
85
  // src/client.ts
86
+ var DEFAULT_BASE_URL = "https://app.studiolayer.io";
86
87
  var StudioLayerClient = class {
87
88
  constructor(options) {
88
89
  if (!options.apiKey) throw new Error("StudioLayerClient: `apiKey` is required");
89
- if (!options.baseUrl) throw new Error("StudioLayerClient: `baseUrl` is required");
90
90
  this.apiKey = options.apiKey;
91
- this.baseUrl = options.baseUrl.replace(/\/+$/, "");
91
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
92
92
  const resolvedFetch = options.fetch ?? globalThis.fetch;
93
93
  if (!resolvedFetch) {
94
94
  throw new Error("StudioLayerClient: no `fetch` available; pass one via options.fetch (Node < 18)");
@@ -274,6 +274,6 @@ function createClient(options) {
274
274
  return new StudioLayerClient(options);
275
275
  }
276
276
 
277
- export { ContentCache, DatasetHandle, MemoryCacheStore, StudioLayerClient, StudioLayerError, createClient };
277
+ export { ContentCache, DEFAULT_BASE_URL, DatasetHandle, MemoryCacheStore, StudioLayerClient, StudioLayerError, createClient };
278
278
  //# sourceMappingURL=index.js.map
279
279
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cache.ts","../src/errors.ts","../src/client.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Read caching for the content client. GET responses (schema, record lists,\n * single records, queries) are cached by request key; writes invalidate the\n * affected keys automatically. Swap in a custom `CacheStore` to share a cache\n * across client instances or to back it with something persistent.\n */\n\nexport interface CacheEntry {\n value: unknown\n /** Epoch ms when the entry expires. `0` means it never expires. */\n expiresAt: number\n}\n\n/** Pluggable backing store. The default is an in-memory, insertion-ordered map. */\nexport interface CacheStore {\n get(key: string): CacheEntry | undefined\n set(key: string, entry: CacheEntry): void\n delete(key: string): void\n /** Iterate current keys (used for prefix invalidation). */\n keys(): Iterable<string>\n clear(): void\n}\n\nexport interface CacheOptions {\n /** Master switch. Default `true`. */\n enabled?: boolean\n /** Time-to-live for cached reads, in ms. Default `60000`. `0` = never expires. */\n ttl?: number\n /** Soft cap on entries; oldest are evicted first. Default `500`. */\n maxEntries?: number\n /** Custom backing store. Defaults to an in-memory store. */\n store?: CacheStore\n}\n\n/** In-memory store with insertion-order (FIFO) eviction once `maxEntries` is hit. */\nexport class MemoryCacheStore implements CacheStore {\n private map = new Map<string, CacheEntry>()\n\n constructor(private maxEntries = 500) {}\n\n get(key: string): CacheEntry | undefined {\n return this.map.get(key)\n }\n\n set(key: string, entry: CacheEntry): void {\n // Re-insert so recently written keys are considered newest.\n this.map.delete(key)\n this.map.set(key, entry)\n while (this.map.size > this.maxEntries) {\n const oldest = this.map.keys().next().value\n if (oldest === undefined) break\n this.map.delete(oldest)\n }\n }\n\n delete(key: string): void {\n this.map.delete(key)\n }\n\n keys(): Iterable<string> {\n return this.map.keys()\n }\n\n clear(): void {\n this.map.clear()\n }\n}\n\n/**\n * Thin cache facade the client talks to. Owns TTL logic and prefix-based\n * invalidation so the client only deals in cache keys.\n */\nexport class ContentCache {\n readonly enabled: boolean\n private ttl: number\n private store: CacheStore\n\n constructor(opts: CacheOptions = {}) {\n this.enabled = opts.enabled ?? true\n this.ttl = opts.ttl ?? 60_000\n this.store = opts.store ?? new MemoryCacheStore(opts.maxEntries ?? 500)\n }\n\n /** Return a fresh cached value for `key`, or `undefined` on miss/expiry. */\n get<T>(key: string, now: number): T | undefined {\n if (!this.enabled) return undefined\n const entry = this.store.get(key)\n if (!entry) return undefined\n if (entry.expiresAt !== 0 && entry.expiresAt <= now) {\n this.store.delete(key)\n return undefined\n }\n return entry.value as T\n }\n\n set(key: string, value: unknown, now: number): void {\n if (!this.enabled) return\n this.store.set(key, { value, expiresAt: this.ttl === 0 ? 0 : now + this.ttl })\n }\n\n /** Drop one exact key. */\n delete(key: string): void {\n this.store.delete(key)\n }\n\n /** Drop every key that starts with `prefix`. */\n deletePrefix(prefix: string): void {\n for (const key of [...this.store.keys()]) {\n if (key.startsWith(prefix)) this.store.delete(key)\n }\n }\n\n clear(): void {\n this.store.clear()\n }\n}\n","/**\n * Error thrown for any non-2xx response from the content API. `status` carries\n * the HTTP status code so callers can branch (401 bad key, 403 out of scope,\n * 404 missing node/dataset/record, 422 query failed).\n */\nexport class StudioLayerError extends Error {\n readonly status: number\n /** The raw parsed response body, when the server returned one. */\n readonly body: unknown\n\n constructor(message: string, status: number, body?: unknown) {\n super(message)\n this.name = 'StudioLayerError'\n this.status = status\n this.body = body\n // Restore prototype chain for `instanceof` when transpiled to ES5.\n Object.setPrototypeOf(this, StudioLayerError.prototype)\n }\n\n get isUnauthorized(): boolean {\n return this.status === 401\n }\n\n get isForbidden(): boolean {\n return this.status === 403\n }\n\n get isNotFound(): boolean {\n return this.status === 404\n }\n}\n","import { ContentCache, type CacheOptions } from './cache'\nimport { StudioLayerError } from './errors'\nimport type { ContentRecord, ContentSchema, QueryResult, SchemaNode } from './types'\n\nexport interface StudioLayerClientOptions {\n /**\n * Project API key, `slk_...`. Mint one in the project's settings under\n * \"API keys\". Its reach is the union of its per-node read/write scopes.\n */\n apiKey: string\n /**\n * Base URL of your StudioLayer server, without a trailing `/api/content`\n * (that path is appended automatically), e.g. `https://studio.example.com`.\n */\n baseUrl: string\n /** Read caching. Pass `false` to disable, or an options object to tune it. */\n cache?: boolean | CacheOptions\n /** Custom `fetch` implementation. Defaults to the global `fetch`. */\n fetch?: typeof fetch\n /** Extra headers merged into every request. */\n headers?: Record<string, string>\n}\n\n/** Per-call read options. */\nexport interface ReadOptions {\n /** Set `false` to bypass the cache for this call and always hit the network. */\n cache?: boolean\n}\n\ntype Method = 'GET' | 'POST' | 'PATCH'\n\n/**\n * Typed client for the StudioLayer content API: read and write a project's node\n * datasets from any website or app. Reads are cached (see the `cache` option);\n * writes invalidate the affected cache entries automatically.\n *\n * ```ts\n * const studio = new StudioLayerClient({ apiKey: 'slk_...', baseUrl: 'https://studio.example.com' })\n * const posts = await studio.dataset<Post>('blog', 'posts').list()\n * ```\n */\nexport class StudioLayerClient {\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly fetchImpl: typeof fetch\n private readonly headers: Record<string, string>\n private readonly cache: ContentCache\n\n constructor(options: StudioLayerClientOptions) {\n if (!options.apiKey) throw new Error('StudioLayerClient: `apiKey` is required')\n if (!options.baseUrl) throw new Error('StudioLayerClient: `baseUrl` is required')\n\n this.apiKey = options.apiKey\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n const resolvedFetch = options.fetch ?? globalThis.fetch\n if (!resolvedFetch) {\n throw new Error('StudioLayerClient: no `fetch` available; pass one via options.fetch (Node < 18)')\n }\n this.fetchImpl = resolvedFetch.bind(globalThis)\n this.headers = options.headers ?? {}\n\n const cacheOpt = options.cache\n this.cache = new ContentCache(\n cacheOpt === false ? { enabled: false }\n : cacheOpt === true || cacheOpt === undefined ? {}\n : cacheOpt,\n )\n }\n\n // ── Introspection ─────────────────────────────────────────────────────────\n\n /** The full content shape this key can reach: nodes, datasets, field schemas. */\n async schema(opts?: ReadOptions): Promise<ContentSchema> {\n return this.cachedGet<ContentSchema>('schema', '/schema', opts)\n }\n\n /** Shorthand for `schema()` then `.nodes`. */\n async nodes(opts?: ReadOptions): Promise<SchemaNode[]> {\n return (await this.schema(opts)).nodes\n }\n\n // ── Records ───────────────────────────────────────────────────────────────\n\n /** List every record in a dataset (references inflated). Requires read scope. */\n async listRecords<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n opts?: ReadOptions,\n ): Promise<ContentRecord<T>[]> {\n const res = await this.cachedGet<{ records: ContentRecord<T>[] }>(\n recordsKey(nodeSlug, datasetSlug),\n datasetPath(nodeSlug, datasetSlug),\n opts,\n )\n return res.records\n }\n\n /** Read one record by uid. Requires read scope. Throws 404 if it does not exist. */\n async getRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n recordUid: string,\n opts?: ReadOptions,\n ): Promise<ContentRecord<T>> {\n const res = await this.cachedGet<{ record: ContentRecord<T> }>(\n recordKey(nodeSlug, datasetSlug, recordUid),\n `${datasetPath(nodeSlug, datasetSlug)}/${encodeURIComponent(recordUid)}`,\n opts,\n )\n return res.record\n }\n\n /** Create a record. Requires write scope. Invalidates cached reads of the dataset. */\n async createRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n data: Record<string, unknown>,\n ): Promise<ContentRecord<T>> {\n const res = await this.request<{ record: ContentRecord<T> }>(\n 'POST',\n datasetPath(nodeSlug, datasetSlug),\n { data },\n )\n this.invalidateDataset(nodeSlug, datasetSlug)\n return res.record\n }\n\n /**\n * Partially update a record. The patch is merged onto the stored data (omitted\n * top-level fields are preserved). Requires write scope. Invalidates cached\n * reads of the dataset and of this record.\n */\n async updateRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n recordUid: string,\n data: Record<string, unknown>,\n ): Promise<ContentRecord<T>> {\n const res = await this.request<{ record: ContentRecord<T> }>(\n 'PATCH',\n `${datasetPath(nodeSlug, datasetSlug)}/${encodeURIComponent(recordUid)}`,\n { data },\n )\n this.invalidateDataset(nodeSlug, datasetSlug)\n this.cache.delete(recordKey(nodeSlug, datasetSlug, recordUid))\n return res.record\n }\n\n // -- Queries --\n\n /** Run a saved query (`queries.<slug>`) and return its result. Requires read scope. */\n async query<V = unknown>(querySlug: string, opts?: ReadOptions): Promise<QueryResult<V>> {\n return this.cachedGet<QueryResult<V>>(\n `query:${querySlug}`,\n `/queries/${encodeURIComponent(querySlug)}`,\n opts,\n )\n }\n\n // ── Fluent dataset handle ───────────────────────────────────────────────────\n\n /**\n * A fluent, type-parameterised handle to one dataset:\n * `client.dataset<Post>('blog', 'posts').list()`.\n */\n dataset<T = Record<string, unknown>>(nodeSlug: string, datasetSlug: string): DatasetHandle<T> {\n return new DatasetHandle<T>(this, nodeSlug, datasetSlug)\n }\n\n // ── Cache control ───────────────────────────────────────────────────────────\n\n /**\n * Clear cached reads. With no argument, clears everything. Pass a scope to\n * clear just part of it: a node slug, or `{ node, dataset }` for one dataset.\n */\n clearCache(scope?: string | { node: string, dataset?: string }): void {\n if (scope === undefined) {\n this.cache.clear()\n return\n }\n if (typeof scope === 'string') {\n this.cache.deletePrefix(`records:${scope}/`)\n this.cache.deletePrefix(`record:${scope}/`)\n return\n }\n if (scope.dataset) {\n this.invalidateDataset(scope.node, scope.dataset)\n } else {\n this.cache.deletePrefix(`records:${scope.node}/`)\n this.cache.deletePrefix(`record:${scope.node}/`)\n }\n }\n\n /** Drop cached record reads for a dataset, plus all query results (which may aggregate it). */\n private invalidateDataset(nodeSlug: string, datasetSlug: string): void {\n this.cache.delete(recordsKey(nodeSlug, datasetSlug))\n this.cache.deletePrefix(`${recordKey(nodeSlug, datasetSlug, '')}`)\n this.cache.deletePrefix('query:')\n }\n\n // ── Transport ───────────────────────────────────────────────────────────────\n\n private async cachedGet<T>(cacheKey: string, path: string, opts?: ReadOptions): Promise<T> {\n const useCache = opts?.cache !== false\n const now = Date.now()\n if (useCache) {\n const hit = this.cache.get<T>(cacheKey, now)\n if (hit !== undefined) return hit\n }\n const value = await this.request<T>('GET', path)\n if (useCache) this.cache.set(cacheKey, value, now)\n return value\n }\n\n private async request<T>(method: Method, path: string, body?: unknown): Promise<T> {\n const res = await this.fetchImpl(`${this.baseUrl}/api/content${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...this.headers,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n })\n\n if (!res.ok) {\n let message = res.statusText || `Request failed with status ${res.status}`\n let parsed: unknown\n try {\n parsed = await res.json()\n const m = parsed as { message?: unknown, statusMessage?: unknown }\n if (typeof m?.message === 'string') message = m.message\n else if (typeof m?.statusMessage === 'string') message = m.statusMessage\n } catch {\n /* non-JSON error body; keep the status text */\n }\n throw new StudioLayerError(message, res.status, parsed)\n }\n\n if (res.status === 204) return undefined as T\n return res.json() as Promise<T>\n }\n}\n\n/** Fluent, dataset-scoped view returned by `client.dataset()`. */\nexport class DatasetHandle<T = Record<string, unknown>> {\n constructor(\n private readonly client: StudioLayerClient,\n private readonly nodeSlug: string,\n private readonly datasetSlug: string,\n ) {}\n\n list(opts?: ReadOptions): Promise<ContentRecord<T>[]> {\n return this.client.listRecords<T>(this.nodeSlug, this.datasetSlug, opts)\n }\n\n get(recordUid: string, opts?: ReadOptions): Promise<ContentRecord<T>> {\n return this.client.getRecord<T>(this.nodeSlug, this.datasetSlug, recordUid, opts)\n }\n\n create(data: Record<string, unknown>): Promise<ContentRecord<T>> {\n return this.client.createRecord<T>(this.nodeSlug, this.datasetSlug, data)\n }\n\n update(recordUid: string, data: Record<string, unknown>): Promise<ContentRecord<T>> {\n return this.client.updateRecord<T>(this.nodeSlug, this.datasetSlug, recordUid, data)\n }\n\n /** Clear cached reads for this dataset. */\n clearCache(): void {\n this.client.clearCache({ node: this.nodeSlug, dataset: this.datasetSlug })\n }\n}\n\n// ── Cache-key + path helpers ──────────────────────────────────────────────────\n\nfunction datasetPath(nodeSlug: string, datasetSlug: string): string {\n return `/nodes/${encodeURIComponent(nodeSlug)}/datasets/${encodeURIComponent(datasetSlug)}/records`\n}\n\nfunction recordsKey(nodeSlug: string, datasetSlug: string): string {\n return `records:${nodeSlug}/${datasetSlug}`\n}\n\nfunction recordKey(nodeSlug: string, datasetSlug: string, recordUid: string): string {\n return `record:${nodeSlug}/${datasetSlug}/${recordUid}`\n}\n","export { StudioLayerClient, DatasetHandle } from './client'\nexport type { StudioLayerClientOptions, ReadOptions } from './client'\nexport { StudioLayerError } from './errors'\nexport { ContentCache, MemoryCacheStore } from './cache'\nexport type { CacheOptions, CacheStore, CacheEntry } from './cache'\nexport type {\n ContentSchema,\n SchemaNode,\n SchemaDataset,\n SchemaField,\n FieldKind,\n FieldType,\n ContentRecord,\n QueryResult,\n QueryShape,\n} from './types'\n\nimport { StudioLayerClient, type StudioLayerClientOptions } from './client'\n\n/** Convenience factory: `createClient({ apiKey, baseUrl })`. */\nexport function createClient(options: StudioLayerClientOptions): StudioLayerClient {\n return new StudioLayerClient(options)\n}\n"]}
1
+ {"version":3,"sources":["../src/cache.ts","../src/errors.ts","../src/client.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Read caching for the content client. GET responses (schema, record lists,\n * single records, queries) are cached by request key; writes invalidate the\n * affected keys automatically. Swap in a custom `CacheStore` to share a cache\n * across client instances or to back it with something persistent.\n */\n\nexport interface CacheEntry {\n value: unknown\n /** Epoch ms when the entry expires. `0` means it never expires. */\n expiresAt: number\n}\n\n/** Pluggable backing store. The default is an in-memory, insertion-ordered map. */\nexport interface CacheStore {\n get(key: string): CacheEntry | undefined\n set(key: string, entry: CacheEntry): void\n delete(key: string): void\n /** Iterate current keys (used for prefix invalidation). */\n keys(): Iterable<string>\n clear(): void\n}\n\nexport interface CacheOptions {\n /** Master switch. Default `true`. */\n enabled?: boolean\n /** Time-to-live for cached reads, in ms. Default `60000`. `0` = never expires. */\n ttl?: number\n /** Soft cap on entries; oldest are evicted first. Default `500`. */\n maxEntries?: number\n /** Custom backing store. Defaults to an in-memory store. */\n store?: CacheStore\n}\n\n/** In-memory store with insertion-order (FIFO) eviction once `maxEntries` is hit. */\nexport class MemoryCacheStore implements CacheStore {\n private map = new Map<string, CacheEntry>()\n\n constructor(private maxEntries = 500) {}\n\n get(key: string): CacheEntry | undefined {\n return this.map.get(key)\n }\n\n set(key: string, entry: CacheEntry): void {\n // Re-insert so recently written keys are considered newest.\n this.map.delete(key)\n this.map.set(key, entry)\n while (this.map.size > this.maxEntries) {\n const oldest = this.map.keys().next().value\n if (oldest === undefined) break\n this.map.delete(oldest)\n }\n }\n\n delete(key: string): void {\n this.map.delete(key)\n }\n\n keys(): Iterable<string> {\n return this.map.keys()\n }\n\n clear(): void {\n this.map.clear()\n }\n}\n\n/**\n * Thin cache facade the client talks to. Owns TTL logic and prefix-based\n * invalidation so the client only deals in cache keys.\n */\nexport class ContentCache {\n readonly enabled: boolean\n private ttl: number\n private store: CacheStore\n\n constructor(opts: CacheOptions = {}) {\n this.enabled = opts.enabled ?? true\n this.ttl = opts.ttl ?? 60_000\n this.store = opts.store ?? new MemoryCacheStore(opts.maxEntries ?? 500)\n }\n\n /** Return a fresh cached value for `key`, or `undefined` on miss/expiry. */\n get<T>(key: string, now: number): T | undefined {\n if (!this.enabled) return undefined\n const entry = this.store.get(key)\n if (!entry) return undefined\n if (entry.expiresAt !== 0 && entry.expiresAt <= now) {\n this.store.delete(key)\n return undefined\n }\n return entry.value as T\n }\n\n set(key: string, value: unknown, now: number): void {\n if (!this.enabled) return\n this.store.set(key, { value, expiresAt: this.ttl === 0 ? 0 : now + this.ttl })\n }\n\n /** Drop one exact key. */\n delete(key: string): void {\n this.store.delete(key)\n }\n\n /** Drop every key that starts with `prefix`. */\n deletePrefix(prefix: string): void {\n for (const key of [...this.store.keys()]) {\n if (key.startsWith(prefix)) this.store.delete(key)\n }\n }\n\n clear(): void {\n this.store.clear()\n }\n}\n","/**\n * Error thrown for any non-2xx response from the content API. `status` carries\n * the HTTP status code so callers can branch (401 bad key, 403 out of scope,\n * 404 missing node/dataset/record, 422 query failed).\n */\nexport class StudioLayerError extends Error {\n readonly status: number\n /** The raw parsed response body, when the server returned one. */\n readonly body: unknown\n\n constructor(message: string, status: number, body?: unknown) {\n super(message)\n this.name = 'StudioLayerError'\n this.status = status\n this.body = body\n // Restore prototype chain for `instanceof` when transpiled to ES5.\n Object.setPrototypeOf(this, StudioLayerError.prototype)\n }\n\n get isUnauthorized(): boolean {\n return this.status === 401\n }\n\n get isForbidden(): boolean {\n return this.status === 403\n }\n\n get isNotFound(): boolean {\n return this.status === 404\n }\n}\n","import { ContentCache, type CacheOptions } from './cache'\nimport { StudioLayerError } from './errors'\nimport type { ContentRecord, ContentSchema, QueryResult, SchemaNode } from './types'\n\n/** Hosted StudioLayer platform. Used when no `baseUrl` is provided. */\nexport const DEFAULT_BASE_URL = 'https://app.studiolayer.io'\n\nexport interface StudioLayerClientOptions {\n /**\n * Project API key, `slk_...`. Mint one in the project's settings under\n * \"API keys\". Its reach is the union of its per-node read/write scopes.\n */\n apiKey: string\n /**\n * Base URL of your StudioLayer server, without a trailing `/api/content`\n * (that path is appended automatically). Defaults to the hosted platform at\n * `https://app.studiolayer.io`; override it for a self-hosted instance.\n */\n baseUrl?: string\n /** Read caching. Pass `false` to disable, or an options object to tune it. */\n cache?: boolean | CacheOptions\n /** Custom `fetch` implementation. Defaults to the global `fetch`. */\n fetch?: typeof fetch\n /** Extra headers merged into every request. */\n headers?: Record<string, string>\n}\n\n/** Per-call read options. */\nexport interface ReadOptions {\n /** Set `false` to bypass the cache for this call and always hit the network. */\n cache?: boolean\n}\n\ntype Method = 'GET' | 'POST' | 'PATCH'\n\n/**\n * Typed client for the StudioLayer content API: read and write a project's node\n * datasets from any website or app. Reads are cached (see the `cache` option);\n * writes invalidate the affected cache entries automatically.\n *\n * ```ts\n * const studio = new StudioLayerClient({ apiKey: 'slk_...', baseUrl: 'https://studio.example.com' })\n * const posts = await studio.dataset<Post>('blog', 'posts').list()\n * ```\n */\nexport class StudioLayerClient {\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly fetchImpl: typeof fetch\n private readonly headers: Record<string, string>\n private readonly cache: ContentCache\n\n constructor(options: StudioLayerClientOptions) {\n if (!options.apiKey) throw new Error('StudioLayerClient: `apiKey` is required')\n\n this.apiKey = options.apiKey\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '')\n const resolvedFetch = options.fetch ?? globalThis.fetch\n if (!resolvedFetch) {\n throw new Error('StudioLayerClient: no `fetch` available; pass one via options.fetch (Node < 18)')\n }\n this.fetchImpl = resolvedFetch.bind(globalThis)\n this.headers = options.headers ?? {}\n\n const cacheOpt = options.cache\n this.cache = new ContentCache(\n cacheOpt === false ? { enabled: false }\n : cacheOpt === true || cacheOpt === undefined ? {}\n : cacheOpt,\n )\n }\n\n // ── Introspection ─────────────────────────────────────────────────────────\n\n /** The full content shape this key can reach: nodes, datasets, field schemas. */\n async schema(opts?: ReadOptions): Promise<ContentSchema> {\n return this.cachedGet<ContentSchema>('schema', '/schema', opts)\n }\n\n /** Shorthand for `schema()` then `.nodes`. */\n async nodes(opts?: ReadOptions): Promise<SchemaNode[]> {\n return (await this.schema(opts)).nodes\n }\n\n // ── Records ───────────────────────────────────────────────────────────────\n\n /** List every record in a dataset (references inflated). Requires read scope. */\n async listRecords<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n opts?: ReadOptions,\n ): Promise<ContentRecord<T>[]> {\n const res = await this.cachedGet<{ records: ContentRecord<T>[] }>(\n recordsKey(nodeSlug, datasetSlug),\n datasetPath(nodeSlug, datasetSlug),\n opts,\n )\n return res.records\n }\n\n /** Read one record by uid. Requires read scope. Throws 404 if it does not exist. */\n async getRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n recordUid: string,\n opts?: ReadOptions,\n ): Promise<ContentRecord<T>> {\n const res = await this.cachedGet<{ record: ContentRecord<T> }>(\n recordKey(nodeSlug, datasetSlug, recordUid),\n `${datasetPath(nodeSlug, datasetSlug)}/${encodeURIComponent(recordUid)}`,\n opts,\n )\n return res.record\n }\n\n /** Create a record. Requires write scope. Invalidates cached reads of the dataset. */\n async createRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n data: Record<string, unknown>,\n ): Promise<ContentRecord<T>> {\n const res = await this.request<{ record: ContentRecord<T> }>(\n 'POST',\n datasetPath(nodeSlug, datasetSlug),\n { data },\n )\n this.invalidateDataset(nodeSlug, datasetSlug)\n return res.record\n }\n\n /**\n * Partially update a record. The patch is merged onto the stored data (omitted\n * top-level fields are preserved). Requires write scope. Invalidates cached\n * reads of the dataset and of this record.\n */\n async updateRecord<T = Record<string, unknown>>(\n nodeSlug: string,\n datasetSlug: string,\n recordUid: string,\n data: Record<string, unknown>,\n ): Promise<ContentRecord<T>> {\n const res = await this.request<{ record: ContentRecord<T> }>(\n 'PATCH',\n `${datasetPath(nodeSlug, datasetSlug)}/${encodeURIComponent(recordUid)}`,\n { data },\n )\n this.invalidateDataset(nodeSlug, datasetSlug)\n this.cache.delete(recordKey(nodeSlug, datasetSlug, recordUid))\n return res.record\n }\n\n // -- Queries --\n\n /** Run a saved query (`queries.<slug>`) and return its result. Requires read scope. */\n async query<V = unknown>(querySlug: string, opts?: ReadOptions): Promise<QueryResult<V>> {\n return this.cachedGet<QueryResult<V>>(\n `query:${querySlug}`,\n `/queries/${encodeURIComponent(querySlug)}`,\n opts,\n )\n }\n\n // ── Fluent dataset handle ───────────────────────────────────────────────────\n\n /**\n * A fluent, type-parameterised handle to one dataset:\n * `client.dataset<Post>('blog', 'posts').list()`.\n */\n dataset<T = Record<string, unknown>>(nodeSlug: string, datasetSlug: string): DatasetHandle<T> {\n return new DatasetHandle<T>(this, nodeSlug, datasetSlug)\n }\n\n // ── Cache control ───────────────────────────────────────────────────────────\n\n /**\n * Clear cached reads. With no argument, clears everything. Pass a scope to\n * clear just part of it: a node slug, or `{ node, dataset }` for one dataset.\n */\n clearCache(scope?: string | { node: string, dataset?: string }): void {\n if (scope === undefined) {\n this.cache.clear()\n return\n }\n if (typeof scope === 'string') {\n this.cache.deletePrefix(`records:${scope}/`)\n this.cache.deletePrefix(`record:${scope}/`)\n return\n }\n if (scope.dataset) {\n this.invalidateDataset(scope.node, scope.dataset)\n } else {\n this.cache.deletePrefix(`records:${scope.node}/`)\n this.cache.deletePrefix(`record:${scope.node}/`)\n }\n }\n\n /** Drop cached record reads for a dataset, plus all query results (which may aggregate it). */\n private invalidateDataset(nodeSlug: string, datasetSlug: string): void {\n this.cache.delete(recordsKey(nodeSlug, datasetSlug))\n this.cache.deletePrefix(`${recordKey(nodeSlug, datasetSlug, '')}`)\n this.cache.deletePrefix('query:')\n }\n\n // ── Transport ───────────────────────────────────────────────────────────────\n\n private async cachedGet<T>(cacheKey: string, path: string, opts?: ReadOptions): Promise<T> {\n const useCache = opts?.cache !== false\n const now = Date.now()\n if (useCache) {\n const hit = this.cache.get<T>(cacheKey, now)\n if (hit !== undefined) return hit\n }\n const value = await this.request<T>('GET', path)\n if (useCache) this.cache.set(cacheKey, value, now)\n return value\n }\n\n private async request<T>(method: Method, path: string, body?: unknown): Promise<T> {\n const res = await this.fetchImpl(`${this.baseUrl}/api/content${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...this.headers,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n })\n\n if (!res.ok) {\n let message = res.statusText || `Request failed with status ${res.status}`\n let parsed: unknown\n try {\n parsed = await res.json()\n const m = parsed as { message?: unknown, statusMessage?: unknown }\n if (typeof m?.message === 'string') message = m.message\n else if (typeof m?.statusMessage === 'string') message = m.statusMessage\n } catch {\n /* non-JSON error body; keep the status text */\n }\n throw new StudioLayerError(message, res.status, parsed)\n }\n\n if (res.status === 204) return undefined as T\n return res.json() as Promise<T>\n }\n}\n\n/** Fluent, dataset-scoped view returned by `client.dataset()`. */\nexport class DatasetHandle<T = Record<string, unknown>> {\n constructor(\n private readonly client: StudioLayerClient,\n private readonly nodeSlug: string,\n private readonly datasetSlug: string,\n ) {}\n\n list(opts?: ReadOptions): Promise<ContentRecord<T>[]> {\n return this.client.listRecords<T>(this.nodeSlug, this.datasetSlug, opts)\n }\n\n get(recordUid: string, opts?: ReadOptions): Promise<ContentRecord<T>> {\n return this.client.getRecord<T>(this.nodeSlug, this.datasetSlug, recordUid, opts)\n }\n\n create(data: Record<string, unknown>): Promise<ContentRecord<T>> {\n return this.client.createRecord<T>(this.nodeSlug, this.datasetSlug, data)\n }\n\n update(recordUid: string, data: Record<string, unknown>): Promise<ContentRecord<T>> {\n return this.client.updateRecord<T>(this.nodeSlug, this.datasetSlug, recordUid, data)\n }\n\n /** Clear cached reads for this dataset. */\n clearCache(): void {\n this.client.clearCache({ node: this.nodeSlug, dataset: this.datasetSlug })\n }\n}\n\n// ── Cache-key + path helpers ──────────────────────────────────────────────────\n\nfunction datasetPath(nodeSlug: string, datasetSlug: string): string {\n return `/nodes/${encodeURIComponent(nodeSlug)}/datasets/${encodeURIComponent(datasetSlug)}/records`\n}\n\nfunction recordsKey(nodeSlug: string, datasetSlug: string): string {\n return `records:${nodeSlug}/${datasetSlug}`\n}\n\nfunction recordKey(nodeSlug: string, datasetSlug: string, recordUid: string): string {\n return `record:${nodeSlug}/${datasetSlug}/${recordUid}`\n}\n","export { StudioLayerClient, DatasetHandle, DEFAULT_BASE_URL } from './client'\nexport type { StudioLayerClientOptions, ReadOptions } from './client'\nexport { StudioLayerError } from './errors'\nexport { ContentCache, MemoryCacheStore } from './cache'\nexport type { CacheOptions, CacheStore, CacheEntry } from './cache'\nexport type {\n ContentSchema,\n SchemaNode,\n SchemaDataset,\n SchemaField,\n FieldKind,\n FieldType,\n ContentRecord,\n QueryResult,\n QueryShape,\n} from './types'\n\nimport { StudioLayerClient, type StudioLayerClientOptions } from './client'\n\n/** Convenience factory: `createClient({ apiKey, baseUrl })`. */\nexport function createClient(options: StudioLayerClientOptions): StudioLayerClient {\n return new StudioLayerClient(options)\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studiolayer/client",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Typed client for the StudioLayer content API. Read and write project node datasets from any website or app, headless-CMS style and beyond.",
5
5
  "type": "module",
6
6
  "publishConfig": {