lazypock 0.1.0 → 0.1.2
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 +145 -0
- package/dist/chunk-3MZJ5HCD.js +190 -0
- package/dist/chunk-3MZJ5HCD.js.map +1 -0
- package/dist/cli.cjs +235 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.global.js +243 -0
- package/dist/cli.global.js.map +1 -0
- package/dist/cli.js +105 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +360 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +298 -77
- package/dist/index.d.ts +298 -77
- package/dist/index.global.js +352 -10
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +178 -10
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
- package/src/cli.ts +145 -0
- package/src/client.ts +48 -0
- package/src/codegen.ts +149 -0
- package/src/collection.ts +145 -21
- package/src/index.ts +29 -408
- package/src/lazypock.ts +490 -0
- package/src/realtime.ts +38 -0
- package/src/schema.ts +39 -0
- package/src/typegen.ts +137 -0
- package/src/types.ts +36 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/http.ts","../src/auth.ts","../src/collection.ts","../src/realtime.ts","../src/files.ts","../src/index.ts"],"sourcesContent":["// ── Record & Collection types ───────────────────────────\n\n/** Shape of a record returned from any collection. */\nexport interface ApiRecord {\n\tid: string;\n\tcollectionId: string;\n\tcollectionName: string;\n\tcreated: string;\n\tupdated: string;\n\t[key: string]: unknown;\n}\n\n/** Paginated list response matching PocketBase format. */\nexport interface ListResult<T = ApiRecord> {\n\titems: T[];\n\tpage: number;\n\tperPage: number;\n\ttotalItems: number;\n\ttotalPages: number;\n}\n\n/** HTTP method supported by the client. */\nexport type Method = \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\";\n\nexport interface RequestOptions {\n\t/** Search/filter params */\n\tparams?: Record<string, string>;\n\t/** Raw request headers to merge */\n\theaders?: Record<string, string>;\n\t/** Abort signal */\n\tsignal?: AbortSignal;\n\t/** Custom fetch implementation (for RN or test mocking) */\n\tfetch?: typeof globalThis.fetch;\n}\n\nexport class ApiError extends Error {\n\treadonly data: unknown;\n\treadonly status: number;\n\n\tconstructor(message: string, data: unknown, status: number) {\n\t\tsuper(message);\n\t\tthis.name = \"ApiError\";\n\t\tthis.data = data;\n\t\tthis.status = status;\n\t}\n}\n","// ── HTTP Client ─────────────────────────────────────────\n// Only relies on globalThis.fetch — works in browser, React Native, and Node 18+\n\nimport { ApiError, type Method, type RequestOptions } from \"./types\";\nimport type { AuthStore } from \"./auth\";\n\n/**\n * Low-level HTTP client wrapping `fetch` with automatic auth token injection.\n * Only relies on `globalThis.fetch` — works in browser, React Native, and Node 18+.\n */\nexport class HttpClient {\n\tprivate baseUrl: string;\n\tprivate authStore: AuthStore;\n\tprivate defaultFetch: typeof globalThis.fetch;\n\n\t/**\n\t * @param baseUrl The API base URL (e.g. `http://localhost:4000/api`). Trailing slash stripped.\n\t * @param authStore The auth store providing the token for Authorization headers.\n\t */\n\tconstructor(baseUrl: string, authStore: AuthStore) {\n\t\tthis.baseUrl = baseUrl.replace(/\\/+$/, \"\");\n\t\tthis.authStore = authStore;\n\t\tthis.defaultFetch = globalThis.fetch.bind(globalThis);\n\t}\n\n\tprivate async refreshAuth(): Promise<{\n\t\ttoken: string;\n\t\trecord: Record<string, unknown>;\n\t} | null> {\n\t\tconst collection = this.authStore.collectionName;\n\t\tif (!collection) return null;\n\t\ttry {\n\t\t\tconst url =\n\t\t\t\tthis.baseUrl + \"/\" + encodeURIComponent(collection) + \"/auth-refresh\";\n\t\t\tconst headers: Record<string, string> = {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t};\n\t\t\tif (this.authStore.token) {\n\t\t\t\theaders[\"Authorization\"] = \"Bearer \" + this.authStore.token;\n\t\t\t}\n\t\t\tconst res = await this.defaultFetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders,\n\t\t\t});\n\t\t\tif (!res.ok) {\n\t\t\t\tthis.authStore.clear();\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst data = (await res.json()) as Record<string, unknown>;\n\t\t\tif (data && typeof data.token === \"string\") {\n\t\t\t\tthis.authStore.set(\n\t\t\t\t\tdata.token,\n\t\t\t\t\t(data.record as Record<string, unknown> as any) ?? null,\n\t\t\t\t);\n\t\t\t\treturn data as { token: string; record: Record<string, unknown> };\n\t\t\t}\n\t\t\treturn null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Make an HTTP request with automatic auth token injection and optional auto-refresh.\n\t *\n\t * @param method HTTP method.\n\t * @param path URL path (appended to baseUrl).\n\t * @param body JSON-serializable body, or FormData for file uploads.\n\t * @param options Optional request options.\n\t * @returns Parsed JSON response, or null for 204 No Content.\n\t * @throws {ApiError} On non-2xx responses.\n\t */\n\tasync request<T = unknown>(\n\t\tmethod: Method,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\t// Auto-refresh if token is expired\n\t\tif (this.authStore.isExpired && this.authStore.collectionName) {\n\t\t\tawait this.refreshAuth();\n\t\t}\n\n\t\tlet url = this.baseUrl + path;\n\t\tif (options?.params) {\n\t\t\tconst qs = new URLSearchParams(options.params).toString();\n\t\t\tif (qs) {\n\t\t\t\turl += (path.includes(\"?\") ? \"&\" : \"?\") + qs;\n\t\t\t}\n\t\t}\n\t\tconst headers: Record<string, string> = {\n\t\t\t...options?.headers,\n\t\t};\n\n\t\t// Don't set Content-Type for FormData (browser sets multipart boundary)\n\t\tif (!(body instanceof FormData)) {\n\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t}\n\n\t\tif (this.authStore.token) {\n\t\t\theaders[\"Authorization\"] = \"Bearer \" + this.authStore.token;\n\t\t}\n\n\t\tconst init: RequestInit = {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\tsignal: options?.signal,\n\t\t};\n\n\t\tif (body != null && method !== \"GET\" && method !== \"DELETE\") {\n\t\t\tif (body instanceof FormData) {\n\t\t\t\tinit.body = body;\n\t\t\t} else {\n\t\t\t\tinit.body = JSON.stringify(body);\n\t\t\t}\n\t\t}\n\n\t\tconst fetcher = options?.fetch ?? this.defaultFetch;\n\t\tconst res = await fetcher(url, init);\n\n\t\tif (res.status === 204) return null;\n\n\t\t// Safely parse JSON — some errored responses may have empty or non-JSON bodies\n\t\tlet bodyText = \"\";\n\t\tlet data: Record<string, unknown> = {};\n\t\ttry {\n\t\t\tbodyText = await res.text();\n\t\t\tif (bodyText) {\n\t\t\t\tdata = JSON.parse(bodyText) as Record<string, unknown>;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Not JSON — keep data as empty object\n\t\t}\n\n\t\tif (!res.ok) {\n\t\t\tthrow new ApiError(\n\t\t\t\t(typeof data.message === \"string\" ? data.message : res.statusText) ||\n\t\t\t\t\t`Request failed with status ${res.status}`,\n\t\t\t\tdata,\n\t\t\t\tres.status,\n\t\t\t);\n\t\t}\n\n\t\treturn data as T;\n\t}\n\n\t/**\n\t * HTTP GET.\n\t * @param path URL path.\n\t * @param options Optional request options.\n\t */\n\tget<T = unknown>(path: string, options?: RequestOptions): Promise<T | null> {\n\t\treturn this.request<T>(\"GET\", path, undefined, options);\n\t}\n\n\t/**\n\t * HTTP POST.\n\t * @param path URL path.\n\t * @param body Optional request body.\n\t * @param options Optional request options.\n\t */\n\tpost<T = unknown>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.request<T>(\"POST\", path, body, options);\n\t}\n\n\t/**\n\t * HTTP PATCH.\n\t * @param path URL path.\n\t * @param body Optional request body.\n\t * @param options Optional request options.\n\t */\n\tpatch<T = unknown>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.request<T>(\"PATCH\", path, body, options);\n\t}\n\n\t/**\n\t * HTTP DELETE.\n\t * @param path URL path.\n\t * @param options Optional request options.\n\t */\n\tdelete<T = unknown>(\n\t\tpath: string,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.request<T>(\"DELETE\", path, undefined, options);\n\t}\n}\n","// ── Auth Store ──────────────────────────────────────────\n// Pluggable storage adapter: swap for AsyncStorage on RN, localStorage on web, etc.\n\n/**\n * Interface for pluggable persistence backends.\n * Swap for `AsyncStorage` on React Native, `localStorage` on web, etc.\n */\nexport interface StorageAdapter {\n\t/** Retrieve a stored value by key. */\n\tget(key: string): string | null | Promise<string | null>;\n\t/** Persist a key-value pair. */\n\tset(key: string, value: string): void | Promise<void>;\n\t/** Remove a stored value by key. */\n\tremove(key: string): void | Promise<void>;\n}\n\n/** Shape of an authenticated user record (from auth collections). */\nexport interface AuthModel {\n\tid: string;\n\t[key: string]: unknown;\n}\n\n/** Callback signature for auth state changes. */\nexport type AuthListener = (model: AuthModel | null, token: string) => void;\n\n// Server token TTL: 7 days (matches Phoenix.Token max_age)\nconst TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport class AuthStore {\n\tprivate _token = \"\";\n\tprivate _model: AuthModel | null = null;\n\tprivate _tokenExpiresAt: number | null = null;\n\tprivate _collectionName: string | null = null;\n\tprivate listeners = new Set<AuthListener>();\n\tprivate storage: StorageAdapter;\n\n\t/**\n\t * Create an AuthStore with optional custom storage adapter.\n\t * @param storage Persistence backend. Defaults to `memoryStorage` (localStorage fallback).\n\t */\n\tconstructor(storage?: StorageAdapter) {\n\t\tthis.storage = storage ?? {\n\t\t\tget: (_key: string) => null,\n\t\t\tset: () => {},\n\t\t\tremove: () => {},\n\t\t};\n\t}\n\n\t/** The current JWT token string, or empty string if not authenticated. */\n\tget token(): string {\n\t\treturn this._token;\n\t}\n\n\t/** The current authenticated user record, or null. */\n\tget model(): AuthModel | null {\n\t\treturn this._model;\n\t}\n\n\t/** Whether a token exists (does not check expiry). */\n\tget isValid(): boolean {\n\t\treturn !!this._token;\n\t}\n\n\t/**\n\t * Whether the current token has expired (with a 30-second buffer).\n\t * Returns false when no expiry has been recorded (e.g. superuser tokens).\n\t */\n\tget isExpired(): boolean {\n\t\treturn (\n\t\t\tthis._tokenExpiresAt !== null &&\n\t\t\tDate.now() >= this._tokenExpiresAt - 30000\n\t\t);\n\t}\n\n\t/** The auth collection name used for automatic token refresh. */\n\tget collectionName(): string | null {\n\t\treturn this._collectionName;\n\t}\n\n\t/**\n\t * Set the auth collection name (used internally by auto-refresh).\n\t * @param name The collection name, or null for superuser tokens.\n\t */\n\tsetCollectionName(name: string | null): void {\n\t\tthis._collectionName = name;\n\t}\n\n\t/**\n\t * Load persisted auth state from storage.\n\t * Should be called once at application startup.\n\t */\n\tasync init(): Promise<void> {\n\t\tconst [token, model, expiresAt] = await Promise.all([\n\t\t\tthis.storage.get(\"auth_token\"),\n\t\t\tthis.storage.get(\"auth_model\"),\n\t\t\tthis.storage.get(\"auth_expires_at\"),\n\t\t]);\n\t\tif (token) this._token = token;\n\t\tif (expiresAt) this._tokenExpiresAt = parseInt(expiresAt, 10) || null;\n\t\tif (model) {\n\t\t\ttry {\n\t\t\t\tthis._model = JSON.parse(model);\n\t\t\t} catch {\n\t\t\t\t// ignore corrupt data\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Update the current auth token and model, persist to storage, and notify listeners.\n\t * @param token The JWT token string.\n\t * @param model The authenticated user record, or null for superusers.\n\t */\n\tset(token: string, model: AuthModel | null): void {\n\t\tthis._token = token;\n\t\tthis._model = model;\n\t\tthis._tokenExpiresAt = Date.now() + TOKEN_TTL_MS;\n\t\tvoid Promise.all([\n\t\t\tthis.storage.set(\"auth_token\", token),\n\t\t\tthis.storage.set(\"auth_expires_at\", String(this._tokenExpiresAt)),\n\t\t\tmodel\n\t\t\t\t? this.storage.set(\"auth_model\", JSON.stringify(model))\n\t\t\t\t: this.storage.remove(\"auth_model\"),\n\t\t]);\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Clear all auth state (token, model, expiry) and notify listeners.\n\t */\n\tclear(): void {\n\t\tthis._token = \"\";\n\t\tthis._model = null;\n\t\tthis._tokenExpiresAt = null;\n\t\tthis._collectionName = null;\n\t\tvoid Promise.all([\n\t\t\tthis.storage.remove(\"auth_token\"),\n\t\t\tthis.storage.remove(\"auth_expires_at\"),\n\t\t\tthis.storage.remove(\"auth_model\"),\n\t\t]);\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Register a listener for auth state changes.\n\t * @param fn Callback invoked with (model, token) on every change.\n\t * @returns An unsubscribe function.\n\t */\n\tonChange(fn: AuthListener): () => void {\n\t\tthis.listeners.add(fn);\n\t\treturn () => this.listeners.delete(fn);\n\t}\n\n\tprivate notify(): void {\n\t\tfor (const fn of this.listeners) {\n\t\t\tfn(this._model, this._token);\n\t\t}\n\t}\n}\n\n// ── Memory-only storage (default, works everywhere) ─────\n/** Default storage adapter using `localStorage` with graceful fallback. */\nexport const memoryStorage: StorageAdapter = {\n\tget(key: string) {\n\t\ttry {\n\t\t\treturn localStorage.getItem(key);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t},\n\tset(key: string, value: string) {\n\t\ttry {\n\t\t\tlocalStorage.setItem(key, value);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t},\n\tremove(key: string) {\n\t\ttry {\n\t\t\tlocalStorage.removeItem(key);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t},\n};\n","// ── Collection Service ──────────────────────────────────\n// Typed CRUD for a single collection (like PocketBase's pb.collection(name))\n\nimport type { HttpClient } from \"./http\";\nimport type { AuthStore, AuthModel } from \"./auth\";\nimport type { ApiRecord, ListResult, RequestOptions } from \"./types\";\n\n/**\n * Typed CRUD service for a single dynamic collection.\n * Get an instance via {@link LazypockClient.collection}.\n */\nexport class CollectionService {\n\tprivate http: HttpClient;\n\tprivate collectionName: string;\n\tprivate authStore?: AuthStore;\n\n\t/** @internal */\n\tconstructor(http: HttpClient, collectionName: string, authStore?: AuthStore) {\n\t\tthis.http = http;\n\t\tthis.collectionName = collectionName;\n\t\tthis.authStore = authStore;\n\t}\n\n\tprivate encodeId(id: string): string {\n\t\treturn encodeURIComponent(id);\n\t}\n\n\t/**\n\t * List records with optional filter/sort/pagination.\n\t * @param params Query parameters including `filter`, `sort`, `page`, `perPage`, `expand`.\n\t * @param options Optional request options.\n\t */\n\tlist<T = ApiRecord>(\n\t\tparams?: Record<string, string>,\n\t\toptions?: RequestOptions,\n\t): Promise<ListResult<T> | null> {\n\t\tconst qs = params ? \"?\" + new URLSearchParams(params).toString() : \"\";\n\t\treturn this.http.get<ListResult<T>>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + qs,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Get a single record by ID.\n\t * @param id Record ID.\n\t * @param options Optional request options.\n\t */\n\tgetOne<T = ApiRecord>(\n\t\tid: string,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.http.get<T>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/\" + this.encodeId(id),\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Create a new record.\n\t * @param data Record fields.\n\t * @param options Optional request options.\n\t */\n\tcreate<T = ApiRecord>(\n\t\tdata: Record<string, unknown>,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.http.post<T>(\n\t\t\t\"/\" + this.encodeId(this.collectionName),\n\t\t\tdata,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Update a record by ID.\n\t * @param id Record ID.\n\t * @param data Updated record fields.\n\t * @param options Optional request options.\n\t */\n\tupdate<T = ApiRecord>(\n\t\tid: string,\n\t\tdata: Record<string, unknown>,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.http.patch<T>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/\" + this.encodeId(id),\n\t\t\tdata,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Delete a record by ID.\n\t * @param id Record ID.\n\t * @param options Optional request options.\n\t */\n\tdelete(id: string, options?: RequestOptions): Promise<null> {\n\t\treturn this.http.delete(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/\" + this.encodeId(id),\n\t\t\toptions,\n\t\t);\n\t}\n\t// ── Expand / Relation Fields ──\n\n\t/**\n\t * Get a list of expandable (relation) fields for this collection.\n\t * Useful for constructing `expand` query parameters.\n\t */\n\tasync expandFields(\n\t\toptions?: RequestOptions,\n\t): Promise<{ field: string; targetCollection: string }[] | null> {\n\t\tconst data = await this.http.get<{\n\t\t\tfields?: { name: string; type: string; options?: Record<string, string> }[];\n\t\t}>(\n\t\t\t\"/collections/\" + this.encodeId(this.collectionName),\n\t\t\toptions,\n\t\t);\n\t\tif (!data?.fields) return null;\n\t\treturn data.fields\n\t\t\t.filter((f) => f.type === \"relation\" && f.options?.collection)\n\t\t\t.map((f) => ({\n\t\t\t\tfield: f.name,\n\t\t\t\ttargetCollection: f.options!.collection,\n\t\t\t}));\n\t}\n\n\t// ── Auth Collection Methods ──\n\n\t/**\n\t * Authenticate with email/password against this auth collection.\n\t * Stores the returned token and user model in the auth store.\n\t */\n\tasync authWithPassword(\n\t\tidentity: string,\n\t\tpassword: string,\n\t\toptions?: RequestOptions,\n\t): Promise<({ token: string; record: ApiRecord } & Record<string, unknown>) | null> {\n\t\tconst data = await this.http.post<\n\t\t\t{ token: string; record: ApiRecord } & Record<string, unknown>\n\t\t>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/auth-with-password\",\n\t\t\t{ identity, password },\n\t\t\toptions,\n\t\t);\n\t\tif (data && this.authStore) {\n\t\t\tthis.authStore.setCollectionName(this.collectionName);\n\t\t\tthis.authStore.set(data.token, data.record as unknown as AuthModel);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * Refresh the auth token for the currently authenticated user.\n\t * Updates the stored token and user model.\n\t */\n\tasync authRefresh(\n\t\toptions?: RequestOptions,\n\t): Promise<({ token: string; record: ApiRecord } & Record<string, unknown>) | null> {\n\t\tconst data = await this.http.post<\n\t\t\t{ token: string; record: ApiRecord } & Record<string, unknown>\n\t\t>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/auth-refresh\",\n\t\t\tundefined,\n\t\t\toptions,\n\t\t);\n\t\tif (data && this.authStore) {\n\t\t\tthis.authStore.setCollectionName(this.collectionName);\n\t\t\tthis.authStore.set(data.token, data.record as unknown as AuthModel);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * Get available auth methods for this collection.\n\t */\n\tasync authMethods(\n\t\toptions?: RequestOptions,\n\t): Promise<Record<string, unknown> | null> {\n\t\treturn this.http.get<Record<string, unknown>>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/auth-methods\",\n\t\t\toptions,\n\t\t);\n\t}\n}\n","// ── Phoenix Channel WebSocket Client ──────────────────\n//\n// Implements the Phoenix Channels protocol over WebSocket\n// to subscribe to collection realtime updates.\n//\n// Protocol: Phoenix V1 JSON Serializer (object-based messages).\n// Sends messages as JSON objects with {topic, event, payload, ref, join_ref}.\n// Receives messages as JSON objects with {topic, event, payload, ref}.\n\ninterface RealtimeEvent {\n\tevent: string;\n\ttopic: string;\n\tpayload: Record<string, unknown>;\n}\n\ninterface SubEntry {\n\ttopic: string;\n\tcallback: (e: RealtimeEvent) => void;\n}\n\nexport type RealtimeConnectOpts = {\n\t/** WebSocket URL (e.g. ws://localhost:4000/socket/websocket) */\n\turl: string;\n\t/** Auth token to pass as query param */\n\ttoken?: string;\n};\n\n/**\n * Derive a WebSocket URL from an HTTP base URL.\n * http://localhost:4000/api → ws://localhost:4000/socket/websocket\n */\nexport function wsUrlFromBaseUrl(baseUrl: string): string {\n\ttry {\n\t\tconst url = new URL(baseUrl);\n\t\tconst protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n\t\treturn `${protocol}//${url.host}/socket/websocket`;\n\t} catch {\n\t\treturn `${baseUrl.replace(/^http/, \"ws\").replace(/\\/api$/, \"\")}/socket/websocket`;\n\t}\n}\n\n/**\n * Phoenix Channel client for real-time collection subscriptions.\n *\n * Connects via WebSocket and subscribes to collection topics.\n * Includes automatic reconnection with exponential backoff.\n *\n * @example\n * ```ts\n * const rt = new RealtimeService();\n * rt.connect({ url: wsUrlFromBaseUrl('http://localhost:4000/api') });\n * rt.subscribe('collection:posts', (e) => console.log(e));\n * ```\n */\nexport class RealtimeService {\n\tprivate ws: WebSocket | null = null;\n\tprivate refCounter = 0;\n\tprivate subscriptions = new Map<string, SubEntry[]>();\n\tprivate reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate reconnectAttempt = 0;\n\tprivate maxReconnectDelay = 5000;\n\n\t// Callbacks for connection state\n\tonReconnect?: () => void;\n\tonDisconnect?: () => void;\n\tonError?: (err: Event) => void;\n\n\tprivate url: string = \"\";\n\tprivate token: string | undefined;\n\n\tconnect(opts: RealtimeConnectOpts): void {\n\t\tthis.url = opts.url;\n\t\tthis.token = opts.token;\n\t\tthis.reconnectAttempt = 0;\n\t\tthis.doConnect();\n\t}\n\n\tdisconnect(): void {\n\t\tthis.clearReconnectTimer();\n\t\tthis.ws?.close();\n\t\tthis.ws = null;\n\t}\n\n\t/**\n\t * Subscribe to a topic (e.g. \"collection:posts\" or \"collection:posts:*\").\n\t * The backend Channel authorizes via listRule on join.\n\t */\n\tsubscribe(topic: string, callback: (e: RealtimeEvent) => void): void {\n\t\tconst subs = this.subscriptions.get(topic) || [];\n\t\tsubs.push({ topic, callback });\n\t\tthis.subscriptions.set(topic, subs);\n\n\t\tif (this.ws?.readyState === WebSocket.OPEN) {\n\t\t\tthis.joinTopic(topic);\n\t\t}\n\t}\n\n\t/**\n\t * Unsubscribe a specific callback from a topic.\n\t */\n\tunsubscribe(topic: string, callback?: (e: RealtimeEvent) => void): void {\n\t\tif (!callback) {\n\t\t\tthis.subscriptions.delete(topic);\n\t\t\treturn;\n\t\t}\n\t\tconst subs = this.subscriptions\n\t\t\t.get(topic)\n\t\t\t?.filter((s) => s.callback !== callback);\n\t\tif (subs && subs.length > 0) {\n\t\t\tthis.subscriptions.set(topic, subs);\n\t\t} else {\n\t\t\tthis.subscriptions.delete(topic);\n\t\t}\n\t}\n\n\tprivate resubscribeAll(): void {\n\t\tfor (const topic of this.subscriptions.keys()) {\n\t\t\tif (this.ws?.readyState === WebSocket.OPEN) {\n\t\t\t\tthis.joinTopic(topic);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate doConnect(): void {\n\t\tif (typeof WebSocket === \"undefined\") {\n\t\t\tconsole.warn(\n\t\t\t\t\"[lazypock] WebSocket not available — realtime subscriptions disabled\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tlet url = this.url;\n\t\tif (this.token) {\n\t\t\turl +=\n\t\t\t\t(url.includes(\"?\") ? \"&\" : \"?\") +\n\t\t\t\t\"token=\" +\n\t\t\t\tencodeURIComponent(this.token);\n\t\t}\n\n\t\tthis.ws = new WebSocket(url);\n\n\t\tthis.ws.onopen = () => {\n\t\t\tthis.reconnectAttempt = 0;\n\t\t\tthis.resubscribeAll();\n\t\t\tthis.startHeartbeat();\n\t\t};\n\n\t\tthis.ws.onmessage = (msg: MessageEvent) => {\n\t\t\tthis.handleMessage(msg.data);\n\t\t};\n\n\t\tthis.ws.onclose = () => {\n\t\t\tthis.stopHeartbeat();\n\t\t\tthis.onDisconnect?.();\n\t\t\tthis.scheduleReconnect();\n\t\t};\n\n\t\tthis.ws.onerror = (err: Event) => {\n\t\t\tthis.onError?.(err);\n\t\t};\n\t}\n\n\tprivate handleMessage(data: string): void {\n\t\tlet parsed: Record<string, unknown>;\n\t\ttry {\n\t\t\tparsed = JSON.parse(data) as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tif (typeof parsed !== \"object\" || !parsed.topic || !parsed.event) return;\n\n\t\tconst topic = parsed.topic as string;\n\t\tconst event = parsed.event as string;\n\t\tconst payload = (parsed.payload as Record<string, unknown>) || {};\n\n\t\t// Handle phx_reply (join/heartbeat responses)\n\t\tif (event === \"phx_reply\") return;\n\n\t\t// Relay incoming events to all subscribers of this topic\n\t\tconst subs = this.subscriptions.get(topic);\n\t\tif (subs) {\n\t\t\tconst e: RealtimeEvent = {\n\t\t\t\tevent,\n\t\t\t\ttopic,\n\t\t\t\tpayload: payload as Record<string, unknown>,\n\t\t\t};\n\t\t\tfor (const s of subs) {\n\t\t\t\ttry {\n\t\t\t\t\ts.callback(e);\n\t\t\t\t} catch {\n\t\t\t\t\t// swallow callback errors\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate joinTopic(topic: string): void {\n\t\tconst ref = this.nextRef();\n\t\t// Phoenix V1 JSON Serializer expects a JSON object, not an array\n\t\tconst msg = JSON.stringify({\n\t\t\ttopic: topic,\n\t\t\tevent: \"phx_join\",\n\t\t\tpayload: {},\n\t\t\tref: ref,\n\t\t});\n\t\tthis.ws?.send(msg);\n\t}\n\n\tprivate nextRef(): string {\n\t\tthis.refCounter++;\n\t\treturn this.refCounter.toString();\n\t}\n\n\t// ── Heartbeat ──\n\n\tprivate heartbeatInterval: ReturnType<typeof setInterval> | null = null;\n\n\tprivate startHeartbeat(): void {\n\t\tthis.stopHeartbeat();\n\t\tthis.heartbeatInterval = setInterval(() => {\n\t\t\tif (this.ws?.readyState === WebSocket.OPEN) {\n\t\t\t\tconst ref = this.nextRef();\n\t\t\t\t// Phoenix V1 JSON Serializer expects a JSON object\n\t\t\t\tconst msg = JSON.stringify({\n\t\t\t\t\ttopic: \"phoenix\",\n\t\t\t\t\tevent: \"heartbeat\",\n\t\t\t\t\tpayload: {},\n\t\t\t\t\tref: ref,\n\t\t\t\t});\n\t\t\t\tthis.ws.send(msg);\n\t\t\t}\n\t\t}, 30_000);\n\t}\n\n\tprivate stopHeartbeat(): void {\n\t\tif (this.heartbeatInterval) {\n\t\t\tclearInterval(this.heartbeatInterval);\n\t\t\tthis.heartbeatInterval = null;\n\t\t}\n\t}\n\n\t// ── Reconnect ──\n\n\tprivate scheduleReconnect(): void {\n\t\tthis.clearReconnectTimer();\n\t\tconst delay = Math.min(\n\t\t\t1000 * 2 ** this.reconnectAttempt,\n\t\t\tthis.maxReconnectDelay,\n\t\t);\n\t\tthis.reconnectAttempt++;\n\t\tthis.reconnectTimer = setTimeout(() => {\n\t\t\tthis.reconnectTimer = null;\n\t\t\tthis.onReconnect?.();\n\t\t\tthis.doConnect();\n\t\t}, delay);\n\t}\n\n\tprivate clearReconnectTimer(): void {\n\t\tif (this.reconnectTimer) {\n\t\t\tclearTimeout(this.reconnectTimer);\n\t\t\tthis.reconnectTimer = null;\n\t\t}\n\t}\n}\n","// ── File Service ─────────────────────────────────────────\n// Upload, download, and delete files.\n\nimport type { HttpClient } from \"./http\";\nimport type { RequestOptions } from \"./types\";\n\n/** Response shape from the server file endpoints */\nexport interface FileRecord {\n\tid: string;\n\tfilename: string;\n\tmimeType: string;\n\tsize: number;\n\turl: string;\n\t[key: string]: unknown;\n}\n\n/**\n * Construct a file URL from the API base URL and file ID.\n */\nexport function getFileUrl(baseUrl: string, fileId: string): string {\n\treturn baseUrl.replace(/\\/+$/, \"\") + \"/files/\" + encodeURIComponent(fileId);\n}\n\n/**\n * Service for file upload, retrieval, and deletion.\n * Access via {@link LazypockClient.files}.\n */\nexport class FilesService {\n\tconstructor(private http: HttpClient) {}\n\n\t/**\n\t * Upload a file or blob.\n\t *\n\t * @param file The File or Blob to upload.\n\t * @param filename Optional filename (required if `file` is a Blob without a name).\n\t * @param options Optional request options (signal, custom fetch).\n\t * @param meta Optional metadata: collectionName, recordId, fieldName for ownership tracking.\n\t */\n\tasync upload(\n\t\tfile: File | Blob,\n\t\tfilename?: string,\n\t\toptions?: RequestOptions,\n\t\tmeta?: { collectionName?: string; recordId?: string; fieldName?: string },\n\t): Promise<FileRecord | null> {\n\t\tif (typeof FormData === \"undefined\") {\n\t\t\tthrow new Error(\"FormData is not available in this environment\");\n\t\t}\n\n\t\tconst formData = new FormData();\n\t\tconst name = filename || (file instanceof File ? file.name : \"file\");\n\t\tformData.append(\"file\", file, name);\n\n\t\tif (meta?.collectionName)\n\t\t\tformData.append(\"collection_name\", meta.collectionName);\n\t\tif (meta?.recordId) formData.append(\"record_id\", meta.recordId);\n\t\tif (meta?.fieldName) formData.append(\"field_name\", meta.fieldName);\n\n\t\tconst data = await this.http.request<Record<string, unknown>>(\n\t\t\t\"POST\",\n\t\t\t\"/files\",\n\t\t\tformData,\n\t\t\toptions,\n\t\t);\n\n\t\treturn data as FileRecord | null;\n\t}\n\n\t/**\n\t * Fetch file metadata including URL.\n\t * @param fileId The file ID.\n\t */\n\tasync getUrl(fileId: string): Promise<string | null> {\n\t\tconst data = await this.http.request<Record<string, unknown>>(\n\t\t\t\"GET\",\n\t\t\t\"/files/\" + encodeURIComponent(fileId),\n\t\t);\n\t\tif (data && typeof data === \"object\" && \"url\" in data) {\n\t\t\treturn (data as Record<string, unknown>).url as string;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Delete a file by ID.\n\t * @param fileId The file ID.\n\t * @param options Optional request options.\n\t */\n\tasync delete(fileId: string, options?: RequestOptions): Promise<null> {\n\t\treturn this.http.request<null>(\n\t\t\t\"DELETE\",\n\t\t\t\"/files/\" + encodeURIComponent(fileId),\n\t\t\tundefined,\n\t\t\toptions,\n\t\t);\n\t}\n}\n","// ── Lazypock SDK — Root Client ─────────────────────────\n// Usage:\n// const client = new LazypockClient({ baseUrl: 'http://localhost:4000/api' });\n// await client.authStore.init();\n// await client.login('admin@example.com', 'password');\n// const records = await client.collection('articles').list();\n\nimport { HttpClient } from \"./http\";\nimport {\n\tAuthStore,\n\tmemoryStorage,\n\ttype StorageAdapter,\n\ttype AuthModel,\n} from \"./auth\";\nimport { CollectionService } from \"./collection\";\nimport {\n\tApiError,\n\ttype ApiRecord,\n\ttype ListResult,\n\ttype RequestOptions,\n} from \"./types\";\nimport { RealtimeService, wsUrlFromBaseUrl } from \"./realtime\";\nimport { FilesService, getFileUrl, type FileRecord } from \"./files\";\n\nexport {\n\tAuthStore,\n\tApiError,\n\tRealtimeService,\n\twsUrlFromBaseUrl,\n\tFilesService,\n\tgetFileUrl,\n};\nexport type {\n\tStorageAdapter,\n\tAuthModel,\n\tApiRecord,\n\tListResult,\n\tRequestOptions,\n\tFileRecord,\n};\n\n/** Options for constructing a {@link LazypockClient}. */\nexport interface LazypockClientOptions {\n\t/** API base URL (e.g. 'http://localhost:4000/api') */\n\tbaseUrl: string;\n\t/** Custom storage adapter (default: localStorage fallback) */\n\tstorage?: StorageAdapter;\n\t/** Explicit auth store instance (for sharing across modules) */\n\tauthStore?: AuthStore;\n\t/** Real-time service for Phoenix Channel WebSocket subscriptions */\n\trealtime?: RealtimeService;\n}\n\n/**\n * Lazypock API client.\n *\n * Provides methods for authentication, CRUD operations on dynamic collections,\n * file management, and real-time subscriptions.\n *\n * @example\n * ```ts\n * const client = new LazypockClient({ baseUrl: 'http://localhost:4000/api' });\n * await client.login('admin@example.com', 'password');\n * const posts = await client.collection('posts').list();\n * ```\n */\nexport class LazypockClient {\n\treadonly http: HttpClient;\n\treadonly authStore: AuthStore;\n\treadonly realtime: RealtimeService;\n\treadonly files: FilesService;\n\tprivate collectionCache = new Map<string, CollectionService>();\n\n\t/**\n\t * Create a new Lazypock client.\n\t * @param options Configuration options.\n\t */\n\tconstructor(options: LazypockClientOptions) {\n\t\tconst baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n\t\tthis.authStore =\n\t\t\toptions.authStore ?? new AuthStore(options.storage ?? memoryStorage);\n\t\tthis.http = new HttpClient(baseUrl, this.authStore);\n\t\tthis.realtime = options.realtime ?? new RealtimeService();\n\t\tthis.files = new FilesService(this.http);\n\t}\n\n\t/**\n\t * Get or create a typed service for the given collection.\n\t * Services are cached after first access.\n\t *\n\t * @param name The collection name.\n\t * @returns A {@link CollectionService} instance.\n\t */\n\tcollection(name: string): CollectionService {\n\t\tlet svc = this.collectionCache.get(name);\n\t\tif (!svc) {\n\t\t\tsvc = new CollectionService(this.http, name, this.authStore);\n\t\t\tthis.collectionCache.set(name, svc);\n\t\t}\n\t\treturn svc as unknown as CollectionService;\n\t}\n\n\t// ── Auth ──\n\n\t/** Check whether any superuser exists (for login vs setup screen routing). */\n\tasync checkSuperuser(): Promise<{ has_superuser: boolean } | null> {\n\t\treturn this.http.get<{ has_superuser: boolean }>(\"/superusers/check\");\n\t}\n\n\t/**\n\t * Create the initial superuser account.\n\t * Only works when no superuser exists yet.\n\t * Stores the returned token in the auth store.\n\t * @param email Superuser email.\n\t * @param password Superuser password (min 8 chars).\n\t */\n\tasync setup(\n\t\temail: string,\n\t\tpassword: string,\n\t): Promise<({ token: string } & Record<string, unknown>) | null> {\n\t\tconst data = await this.http.post<\n\t\t\t{ token: string } & Record<string, unknown>\n\t\t>(\"/superusers/setup\", { email, password });\n\t\tif (data) {\n\t\t\tthis.authStore.setCollectionName(null);\n\t\t\tthis.authStore.set(data.token, null);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * Authenticate as a superuser or auth collection user.\n\t *\n\t * When `collection` is provided, authenticates against\n\t * `/{collection}/auth-with-password`. Otherwise logs in as superuser.\n\t * Stores the returned token in the auth store.\n\t *\n\t * @param email User email or identity.\n\t * @param password User password.\n\t * @param collection Optional auth collection name.\n\t */\n\tasync login(\n\t\temail: string,\n\t\tpassword: string,\n\t\tcollection?: string,\n\t): Promise<({ token: string } & Record<string, unknown>) | null> {\n\t\tlet data;\n\t\tif (collection) {\n\t\t\tdata = await this.http.post<\n\t\t\t\t{ token: string; record: Record<string, unknown> } & Record<\n\t\t\t\t\tstring,\n\t\t\t\t\tunknown\n\t\t\t\t>\n\t\t\t>(\"/\" + encodeURIComponent(collection) + \"/auth-with-password\", {\n\t\t\t\tidentity: email,\n\t\t\t\tpassword,\n\t\t\t});\n\t\t\tif (data && data.record) {\n\t\t\t\tthis.authStore.setCollectionName(collection);\n\t\t\t\tthis.authStore.set(data.token, data.record as unknown as AuthModel);\n\t\t\t}\n\t\t} else {\n\t\t\tdata = await this.http.post<{ token: string } & Record<string, unknown>>(\n\t\t\t\t\"/superusers/login\",\n\t\t\t\t{ email, password },\n\t\t\t);\n\t\t\tif (data) {\n\t\t\t\tthis.authStore.setCollectionName(null);\n\t\t\t\tthis.authStore.set(data.token, null);\n\t\t\t}\n\t\t}\n\t\treturn data;\n\t}\n\n\t/** Fetch the current superuser profile and refresh the auth model. */\n\tasync me<T = ApiRecord>(options?: RequestOptions): Promise<T | null> {\n\t\tconst data = await this.http.get<T>(\"/superusers/me\", options);\n\t\tif (data) {\n\t\t\t// Update the auth model with fresh data\n\t\t\tthis.authStore.set(this.authStore.token, data as unknown as AuthModel);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * Authenticate against an auth collection with email/password.\n\t * Stores the returned token and user record in the auth store.\n\t *\n\t * @param collection The auth collection name.\n\t * @param identity Email or username.\n\t * @param password Password.\n\t * @param options Optional request options.\n\t */\n\tasync authWithPassword(\n\t\tcollection: string,\n\t\tidentity: string,\n\t\tpassword: string,\n\t\toptions?: RequestOptions,\n\t): Promise<\n\t\t({ token: string; record: ApiRecord } & Record<string, unknown>) | null\n\t> {\n\t\tconst data = await this.http.post<\n\t\t\t{ token: string; record: ApiRecord } & Record<string, unknown>\n\t\t>(\n\t\t\t\"/\" + encodeURIComponent(collection) + \"/auth-with-password\",\n\t\t\t{ identity, password },\n\t\t\toptions,\n\t\t);\n\t\tif (data) {\n\t\t\tthis.authStore.setCollectionName(collection);\n\t\t\tthis.authStore.set(data.token, data.record as unknown as AuthModel);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * Refresh an auth collection token.\n\t * Uses the currently stored auth token.\n\t *\n\t * @param collection The auth collection name.\n\t * @param options Optional request options.\n\t */\n\tasync authRefresh(\n\t\tcollection: string,\n\t\toptions?: RequestOptions,\n\t): Promise<\n\t\t({ token: string; record: ApiRecord } & Record<string, unknown>) | null\n\t> {\n\t\tconst data = await this.http.post<\n\t\t\t{ token: string; record: ApiRecord } & Record<string, unknown>\n\t\t>(\n\t\t\t\"/\" + encodeURIComponent(collection) + \"/auth-refresh\",\n\t\t\tundefined,\n\t\t\toptions,\n\t\t);\n\t\tif (data) {\n\t\t\tthis.authStore.setCollectionName(collection);\n\t\t\tthis.authStore.set(data.token, data.record as unknown as AuthModel);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/** Clear the current auth state and remove persisted tokens. */\n\tlogout(): void {\n\t\tthis.authStore.clear();\n\t}\n\n\t// ── Health ──\n\n\t/** Ping the API health endpoint. */\n\thealth(options?: RequestOptions): Promise<Record<string, unknown> | null> {\n\t\treturn this.http.get<Record<string, unknown>>(\"/health\", options);\n\t}\n\n\t// ── Collection Management (admin) ──\n\n\t/**\n\t * List all collections (admin).\n\t * @param q URL query string (e.g. `page=1&perPage=200`).\n\t * @param options Optional request options.\n\t */\n\tlistCollections(\n\t\tq?: string,\n\t\toptions?: RequestOptions,\n\t): Promise<ListResult<ApiRecord> | null> {\n\t\treturn this.http.get<ListResult<ApiRecord>>(\n\t\t\t\"/collections\" + (q ? \"?\" + q : \"\"),\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Get a single collection by ID or name.\n\t * @param id Collection ID or name.\n\t * @param options Optional request options.\n\t */\n\tgetCollection(\n\t\tid: string,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.get<ApiRecord>(\n\t\t\t\"/collections/\" + encodeURIComponent(id),\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Create a new collection (admin).\n\t * @param data Collection definition (name, type, fields, options, rules, etc.).\n\t * @param options Optional request options.\n\t */\n\tcreateCollection(\n\t\tdata: Record<string, unknown>,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.post<ApiRecord>(\"/collections\", data, options);\n\t}\n\n\t/**\n\t * Update an existing collection (admin).\n\t * @param id Collection ID or name.\n\t * @param data Updated collection fields.\n\t * @param options Optional request options.\n\t */\n\tupdateCollection(\n\t\tid: string,\n\t\tdata: Record<string, unknown>,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.patch<ApiRecord>(\n\t\t\t\"/collections/\" + encodeURIComponent(id),\n\t\t\tdata,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Delete a collection (admin).\n\t * @param id Collection ID or name.\n\t * @param options Optional request options.\n\t */\n\tdeleteCollection(id: string, options?: RequestOptions): Promise<null> {\n\t\treturn this.http.delete(\"/collections/\" + encodeURIComponent(id), options);\n\t}\n\n\t// ── Records (dynamic collection) ──\n\n\t/**\n\t * List records from a dynamic collection with optional filter/sort/pagination.\n\t *\n\t * @param coll Collection name.\n\t * @param params Query parameters including:\n\t * - `filter` — PocketBase filter syntax (e.g. `title~'hello' && published=true`)\n\t * - `sort` — Comma-separated, `-` prefix for DESC (e.g. `-created,title`)\n\t * - `page` — Page number (default: 1)\n\t * - `perPage` — Items per page (default: 30, max: 200)\n\t * - `expand` — Comma-separated relation fields (e.g. `author,category`)\n\t * @param options Optional request options.\n\t */\n\tlistRecords(\n\t\tcoll: string,\n\t\tparams?: Record<string, string>,\n\t\toptions?: RequestOptions,\n\t): Promise<ListResult<ApiRecord> | null> {\n\t\tconst qs = params ? \"?\" + new URLSearchParams(params).toString() : \"\";\n\t\treturn this.http.get<ListResult<ApiRecord>>(\n\t\t\t\"/\" + encodeURIComponent(coll) + qs,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Get a single record by ID.\n\t * @param coll Collection name.\n\t * @param id Record ID.\n\t * @param options Optional request options.\n\t */\n\tgetRecord(\n\t\tcoll: string,\n\t\tid: string,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.get<ApiRecord>(\n\t\t\t\"/\" + encodeURIComponent(coll) + \"/\" + encodeURIComponent(id),\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Create a record in a dynamic collection.\n\t * @param coll Collection name.\n\t * @param data Record fields.\n\t * @param options Optional request options.\n\t */\n\tcreateRecord(\n\t\tcoll: string,\n\t\tdata: Record<string, unknown>,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.post<ApiRecord>(\n\t\t\t\"/\" + encodeURIComponent(coll),\n\t\t\tdata,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Update a record in a dynamic collection.\n\t * @param coll Collection name.\n\t * @param id Record ID.\n\t * @param data Updated record fields.\n\t * @param options Optional request options.\n\t */\n\tupdateRecord(\n\t\tcoll: string,\n\t\tid: string,\n\t\tdata: Record<string, unknown>,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.patch<ApiRecord>(\n\t\t\t\"/\" + encodeURIComponent(coll) + \"/\" + encodeURIComponent(id),\n\t\t\tdata,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Delete a record from a dynamic collection.\n\t * @param coll Collection name.\n\t * @param id Record ID.\n\t * @param options Optional request options.\n\t */\n\tdeleteRecord(\n\t\tcoll: string,\n\t\tid: string,\n\t\toptions?: RequestOptions,\n\t): Promise<null> {\n\t\treturn this.http.delete(\n\t\t\t\"/\" + encodeURIComponent(coll) + \"/\" + encodeURIComponent(id),\n\t\t\toptions,\n\t\t);\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/http.ts","../src/auth.ts","../src/collection.ts","../src/realtime.ts","../src/files.ts","../src/lazypock.ts","../src/client.ts"],"sourcesContent":["// ── Record & Collection types ───────────────────────────\n\n/**\n * Base shape every record returned from any collection satisfies.\n * Generated record interfaces extend this.\n */\nexport interface BaseRecordFields {\n\tid: string;\n\tcollectionId: string;\n\tcollectionName: string;\n\tcreated: string;\n\tupdated: string;\n}\n\n/** Shape of a record returned from any collection. */\nexport interface ApiRecord extends BaseRecordFields {\n\t[key: string]: unknown;\n}\n\n/**\n * Structural marker for concrete record shapes (generated or hand-written).\n * Used to differentiate a typed collection service from the untyped default.\n */\nexport type RecordShape = Record<string, unknown>;\n\n/** System fields every record carries — not user-provided on create. */\nexport type SystemFields =\n\t| \"id\"\n\t| \"collectionId\"\n\t| \"collectionName\"\n\t| \"created\"\n\t| \"updated\";\n\n/**\n * Data accepted by `create()`: any subset of `T`'s fields, but\n * never the system fields. Unknown/extra keys are rejected at compile\n * time via excess-property checking (object literals).\n */\nexport type CreateData<T> = Partial<Omit<T, SystemFields>>;\n\n/**\n * Data accepted by `update()`: any subset of `T`'s fields.\n * Unknown/extra keys are rejected at compile time.\n */\nexport type UpdateData<T> = Partial<Omit<T, SystemFields>>;\n\n/** Paginated list response matching PocketBase format. */\nexport interface ListResult<T = ApiRecord> {\n\titems: T[];\n\tpage: number;\n\tperPage: number;\n\ttotalItems: number;\n\ttotalPages: number;\n}\n\n/** HTTP method supported by the client. */\nexport type Method = \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\";\n\nexport interface RequestOptions {\n\t/** Search/filter params */\n\tparams?: Record<string, string>;\n\t/** Raw request headers to merge */\n\theaders?: Record<string, string>;\n\t/** Abort signal */\n\tsignal?: AbortSignal;\n\t/** Custom fetch implementation (for RN or test mocking) */\n\tfetch?: typeof globalThis.fetch;\n}\n\nexport class ApiError extends Error {\n\treadonly data: unknown;\n\treadonly status: number;\n\n\tconstructor(message: string, data: unknown, status: number) {\n\t\tsuper(message);\n\t\tthis.name = \"ApiError\";\n\t\tthis.data = data;\n\t\tthis.status = status;\n\t}\n}\n","// ── HTTP Client ─────────────────────────────────────────\n// Only relies on globalThis.fetch — works in browser, React Native, and Node 18+\n\nimport { ApiError, type Method, type RequestOptions } from \"./types\";\nimport type { AuthStore } from \"./auth\";\n\n/**\n * Low-level HTTP client wrapping `fetch` with automatic auth token injection.\n * Only relies on `globalThis.fetch` — works in browser, React Native, and Node 18+.\n */\nexport class HttpClient {\n\tprivate baseUrl: string;\n\tprivate authStore: AuthStore;\n\tprivate defaultFetch: typeof globalThis.fetch;\n\n\t/**\n\t * @param baseUrl The API base URL (e.g. `http://localhost:4000/api`). Trailing slash stripped.\n\t * @param authStore The auth store providing the token for Authorization headers.\n\t */\n\tconstructor(baseUrl: string, authStore: AuthStore) {\n\t\tthis.baseUrl = baseUrl.replace(/\\/+$/, \"\");\n\t\tthis.authStore = authStore;\n\t\tthis.defaultFetch = globalThis.fetch.bind(globalThis);\n\t}\n\n\tprivate async refreshAuth(): Promise<{\n\t\ttoken: string;\n\t\trecord: Record<string, unknown>;\n\t} | null> {\n\t\tconst collection = this.authStore.collectionName;\n\t\tif (!collection) return null;\n\t\ttry {\n\t\t\tconst url =\n\t\t\t\tthis.baseUrl + \"/\" + encodeURIComponent(collection) + \"/auth-refresh\";\n\t\t\tconst headers: Record<string, string> = {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t};\n\t\t\tif (this.authStore.token) {\n\t\t\t\theaders[\"Authorization\"] = \"Bearer \" + this.authStore.token;\n\t\t\t}\n\t\t\tconst res = await this.defaultFetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders,\n\t\t\t});\n\t\t\tif (!res.ok) {\n\t\t\t\tthis.authStore.clear();\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst data = (await res.json()) as Record<string, unknown>;\n\t\t\tif (data && typeof data.token === \"string\") {\n\t\t\t\tthis.authStore.set(\n\t\t\t\t\tdata.token,\n\t\t\t\t\t(data.record as Record<string, unknown> as any) ?? null,\n\t\t\t\t);\n\t\t\t\treturn data as { token: string; record: Record<string, unknown> };\n\t\t\t}\n\t\t\treturn null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Make an HTTP request with automatic auth token injection and optional auto-refresh.\n\t *\n\t * @param method HTTP method.\n\t * @param path URL path (appended to baseUrl).\n\t * @param body JSON-serializable body, or FormData for file uploads.\n\t * @param options Optional request options.\n\t * @returns Parsed JSON response, or null for 204 No Content.\n\t * @throws {ApiError} On non-2xx responses.\n\t */\n\tasync request<T = unknown>(\n\t\tmethod: Method,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\t// Auto-refresh if token is expired\n\t\tif (this.authStore.isExpired && this.authStore.collectionName) {\n\t\t\tawait this.refreshAuth();\n\t\t}\n\n\t\tlet url = this.baseUrl + path;\n\t\tif (options?.params) {\n\t\t\tconst qs = new URLSearchParams(options.params).toString();\n\t\t\tif (qs) {\n\t\t\t\turl += (path.includes(\"?\") ? \"&\" : \"?\") + qs;\n\t\t\t}\n\t\t}\n\t\tconst headers: Record<string, string> = {\n\t\t\t...options?.headers,\n\t\t};\n\n\t\t// Don't set Content-Type for FormData (browser sets multipart boundary)\n\t\tif (!(body instanceof FormData)) {\n\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t}\n\n\t\tif (this.authStore.token) {\n\t\t\theaders[\"Authorization\"] = \"Bearer \" + this.authStore.token;\n\t\t}\n\n\t\tconst init: RequestInit = {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\tsignal: options?.signal,\n\t\t};\n\n\t\tif (body != null && method !== \"GET\" && method !== \"DELETE\") {\n\t\t\tif (body instanceof FormData) {\n\t\t\t\tinit.body = body;\n\t\t\t} else {\n\t\t\t\tinit.body = JSON.stringify(body);\n\t\t\t}\n\t\t}\n\n\t\tconst fetcher = options?.fetch ?? this.defaultFetch;\n\t\tconst res = await fetcher(url, init);\n\n\t\tif (res.status === 204) return null;\n\n\t\t// Safely parse JSON — some errored responses may have empty or non-JSON bodies\n\t\tlet bodyText = \"\";\n\t\tlet data: Record<string, unknown> = {};\n\t\ttry {\n\t\t\tbodyText = await res.text();\n\t\t\tif (bodyText) {\n\t\t\t\tdata = JSON.parse(bodyText) as Record<string, unknown>;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Not JSON — keep data as empty object\n\t\t}\n\n\t\tif (!res.ok) {\n\t\t\tthrow new ApiError(\n\t\t\t\t(typeof data.message === \"string\" ? data.message : res.statusText) ||\n\t\t\t\t\t`Request failed with status ${res.status}`,\n\t\t\t\tdata,\n\t\t\t\tres.status,\n\t\t\t);\n\t\t}\n\n\t\treturn data as T;\n\t}\n\n\t/**\n\t * HTTP GET.\n\t * @param path URL path.\n\t * @param options Optional request options.\n\t */\n\tget<T = unknown>(path: string, options?: RequestOptions): Promise<T | null> {\n\t\treturn this.request<T>(\"GET\", path, undefined, options);\n\t}\n\n\t/**\n\t * HTTP POST.\n\t * @param path URL path.\n\t * @param body Optional request body.\n\t * @param options Optional request options.\n\t */\n\tpost<T = unknown>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.request<T>(\"POST\", path, body, options);\n\t}\n\n\t/**\n\t * HTTP PATCH.\n\t * @param path URL path.\n\t * @param body Optional request body.\n\t * @param options Optional request options.\n\t */\n\tpatch<T = unknown>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.request<T>(\"PATCH\", path, body, options);\n\t}\n\n\t/**\n\t * HTTP DELETE.\n\t * @param path URL path.\n\t * @param options Optional request options.\n\t */\n\tdelete<T = unknown>(\n\t\tpath: string,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.request<T>(\"DELETE\", path, undefined, options);\n\t}\n}\n","// ── Auth Store ──────────────────────────────────────────\n// Pluggable storage adapter: swap for AsyncStorage on RN, localStorage on web, etc.\n\n/**\n * Interface for pluggable persistence backends.\n * Swap for `AsyncStorage` on React Native, `localStorage` on web, etc.\n */\nexport interface StorageAdapter {\n\t/** Retrieve a stored value by key. */\n\tget(key: string): string | null | Promise<string | null>;\n\t/** Persist a key-value pair. */\n\tset(key: string, value: string): void | Promise<void>;\n\t/** Remove a stored value by key. */\n\tremove(key: string): void | Promise<void>;\n}\n\n/** Shape of an authenticated user record (from auth collections). */\nexport interface AuthModel {\n\tid: string;\n\t[key: string]: unknown;\n}\n\n/** Callback signature for auth state changes. */\nexport type AuthListener = (model: AuthModel | null, token: string) => void;\n\n// Server token TTL: 7 days (matches Phoenix.Token max_age)\nconst TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport class AuthStore {\n\tprivate _token = \"\";\n\tprivate _model: AuthModel | null = null;\n\tprivate _tokenExpiresAt: number | null = null;\n\tprivate _collectionName: string | null = null;\n\tprivate listeners = new Set<AuthListener>();\n\tprivate storage: StorageAdapter;\n\n\t/**\n\t * Create an AuthStore with optional custom storage adapter.\n\t * @param storage Persistence backend. Defaults to `memoryStorage` (localStorage fallback).\n\t */\n\tconstructor(storage?: StorageAdapter) {\n\t\tthis.storage = storage ?? {\n\t\t\tget: (_key: string) => null,\n\t\t\tset: () => {},\n\t\t\tremove: () => {},\n\t\t};\n\t}\n\n\t/** The current JWT token string, or empty string if not authenticated. */\n\tget token(): string {\n\t\treturn this._token;\n\t}\n\n\t/** The current authenticated user record, or null. */\n\tget model(): AuthModel | null {\n\t\treturn this._model;\n\t}\n\n\t/** Whether a token exists (does not check expiry). */\n\tget isValid(): boolean {\n\t\treturn !!this._token;\n\t}\n\n\t/**\n\t * Whether the current token has expired (with a 30-second buffer).\n\t * Returns false when no expiry has been recorded (e.g. superuser tokens).\n\t */\n\tget isExpired(): boolean {\n\t\treturn (\n\t\t\tthis._tokenExpiresAt !== null &&\n\t\t\tDate.now() >= this._tokenExpiresAt - 30000\n\t\t);\n\t}\n\n\t/** The auth collection name used for automatic token refresh. */\n\tget collectionName(): string | null {\n\t\treturn this._collectionName;\n\t}\n\n\t/**\n\t * Set the auth collection name (used internally by auto-refresh).\n\t * @param name The collection name, or null for superuser tokens.\n\t */\n\tsetCollectionName(name: string | null): void {\n\t\tthis._collectionName = name;\n\t}\n\n\t/**\n\t * Load persisted auth state from storage.\n\t * Should be called once at application startup.\n\t */\n\tasync init(): Promise<void> {\n\t\tconst [token, model, expiresAt] = await Promise.all([\n\t\t\tthis.storage.get(\"auth_token\"),\n\t\t\tthis.storage.get(\"auth_model\"),\n\t\t\tthis.storage.get(\"auth_expires_at\"),\n\t\t]);\n\t\tif (token) this._token = token;\n\t\tif (expiresAt) this._tokenExpiresAt = parseInt(expiresAt, 10) || null;\n\t\tif (model) {\n\t\t\ttry {\n\t\t\t\tthis._model = JSON.parse(model);\n\t\t\t} catch {\n\t\t\t\t// ignore corrupt data\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Update the current auth token and model, persist to storage, and notify listeners.\n\t * @param token The JWT token string.\n\t * @param model The authenticated user record, or null for superusers.\n\t */\n\tset(token: string, model: AuthModel | null): void {\n\t\tthis._token = token;\n\t\tthis._model = model;\n\t\tthis._tokenExpiresAt = Date.now() + TOKEN_TTL_MS;\n\t\tvoid Promise.all([\n\t\t\tthis.storage.set(\"auth_token\", token),\n\t\t\tthis.storage.set(\"auth_expires_at\", String(this._tokenExpiresAt)),\n\t\t\tmodel\n\t\t\t\t? this.storage.set(\"auth_model\", JSON.stringify(model))\n\t\t\t\t: this.storage.remove(\"auth_model\"),\n\t\t]);\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Clear all auth state (token, model, expiry) and notify listeners.\n\t */\n\tclear(): void {\n\t\tthis._token = \"\";\n\t\tthis._model = null;\n\t\tthis._tokenExpiresAt = null;\n\t\tthis._collectionName = null;\n\t\tvoid Promise.all([\n\t\t\tthis.storage.remove(\"auth_token\"),\n\t\t\tthis.storage.remove(\"auth_expires_at\"),\n\t\t\tthis.storage.remove(\"auth_model\"),\n\t\t]);\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Register a listener for auth state changes.\n\t * @param fn Callback invoked with (model, token) on every change.\n\t * @returns An unsubscribe function.\n\t */\n\tonChange(fn: AuthListener): () => void {\n\t\tthis.listeners.add(fn);\n\t\treturn () => this.listeners.delete(fn);\n\t}\n\n\tprivate notify(): void {\n\t\tfor (const fn of this.listeners) {\n\t\t\tfn(this._model, this._token);\n\t\t}\n\t}\n}\n\n// ── Memory-only storage (default, works everywhere) ─────\n/** Default storage adapter using `localStorage` with graceful fallback. */\nexport const memoryStorage: StorageAdapter = {\n\tget(key: string) {\n\t\ttry {\n\t\t\treturn localStorage.getItem(key);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t},\n\tset(key: string, value: string) {\n\t\ttry {\n\t\t\tlocalStorage.setItem(key, value);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t},\n\tremove(key: string) {\n\t\ttry {\n\t\t\tlocalStorage.removeItem(key);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t},\n};\n","// ── Collection Service ──────────────────────────────────\n// Typed CRUD for a single collection (like PocketBase's pb.collection(name))\n\nimport type { HttpClient } from \"./http\";\nimport type { AuthStore, AuthModel } from \"./auth\";\nimport type {\n\tApiRecord,\n\tListResult,\n\tRequestOptions,\n\tCreateData,\n\tUpdateData,\n} from \"./types\";\nimport type { RealtimeService } from \"./realtime\";\n\n/**\n * A realtime record-change event delivered to subscription callbacks.\n * Mirrors PocketBase's RealtimeService result shape (`action` + `record`).\n */\nexport interface RealtimeMessage {\n\taction: \"create\" | \"update\" | \"delete\";\n\trecord: Record<string, unknown>;\n\ttopic?: string;\n}\n\n/** Subscription callback for a collection's realtime events. */\nexport type RealtimeCallback = (e: RealtimeMessage) => void;\n\n/** Raw event passed by the low-level realtime service. */\ninterface RealtimeEventLike {\n\tevent: string;\n\ttopic: string;\n\tpayload?: Record<string, unknown>;\n}\n\n/** Map a raw event/action to a normalised create/update/delete action. */\nfunction normalizeAction(\n\tevent: string,\n\trawAction?: unknown,\n): RealtimeMessage[\"action\"] {\n\tif (typeof rawAction === \"string\") {\n\t\tconst a = rawAction.toLowerCase();\n\t\tif (a === \"create\" || a === \"update\" || a === \"delete\") {\n\t\t\treturn a;\n\t\t}\n\t}\n\tif (event === \"record_change\") return \"update\";\n\tif (event === \"create\" || event === \"record_create\") return \"create\";\n\tif (event === \"delete\" || event === \"record_delete\") return \"delete\";\n\treturn \"update\";\n}\n\n/**\n * Typed CRUD service for a single dynamic collection.\n * Get an instance via {@link LazypockClient.collection}.\n *\n * @typeParam T — The record shape for this collection. Defaults to {@link ApiRecord}.\n */\nexport class CollectionService<T = ApiRecord> {\n\tprivate http: HttpClient;\n\tprivate collectionName: string;\n\tprivate authStore?: AuthStore;\n\tprivate realtime?: RealtimeService;\n\n\t/** @internal */\n\tconstructor(\n\t\thttp: HttpClient,\n\t\tcollectionName: string,\n\t\tauthStore?: AuthStore,\n\t\trealtime?: RealtimeService,\n\t) {\n\t\tthis.http = http;\n\t\tthis.collectionName = collectionName;\n\t\tthis.authStore = authStore;\n\t\tthis.realtime = realtime;\n\t}\n\n\tprivate encodeId(id: string): string {\n\t\treturn encodeURIComponent(id);\n\t}\n\n\t/**\n\t * List records with optional filter/sort/pagination.\n\t * @param params Query parameters including `filter`, `sort`, `page`, `perPage`, `expand`.\n\t * @param options Optional request options.\n\t */\n\tlist(\n\t\tparams?: Record<string, string>,\n\t\toptions?: RequestOptions,\n\t): Promise<ListResult<T> | null> {\n\t\tconst qs = params ? \"?\" + new URLSearchParams(params).toString() : \"\";\n\t\treturn this.http.get<ListResult<T>>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + qs,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Get a single record by ID.\n\t * @param id Record ID.\n\t * @param options Optional request options.\n\t */\n\tgetOne(id: string, options?: RequestOptions): Promise<T | null> {\n\t\treturn this.http.get<T>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/\" + this.encodeId(id),\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Create a new record.\n\t * @param data Record fields. When `T` is a concrete shape (e.g. a generated\n\t * record type), excess/unknown fields are rejected at compile time.\n\t * @param options Optional request options.\n\t */\n\tcreate(\n\t\tdata: T extends ApiRecord ? Record<string, unknown> : CreateData<T>,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.http.post<T>(\n\t\t\t\"/\" + this.encodeId(this.collectionName),\n\t\t\tdata,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Update a record by ID.\n\t * @param id Record ID.\n\t * @param data Updated record fields. When `T` is a concrete shape, `data`\n\t * must be a partial of `T` — unknown fields are rejected.\n\t * @param options Optional request options.\n\t */\n\tupdate(\n\t\tid: string,\n\t\tdata: T extends ApiRecord ? Record<string, unknown> : UpdateData<T>,\n\t\toptions?: RequestOptions,\n\t): Promise<T | null> {\n\t\treturn this.http.patch<T>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/\" + this.encodeId(id),\n\t\t\tdata,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Delete a record by ID.\n\t * @param id Record ID.\n\t * @param options Optional request options.\n\t */\n\tdelete(id: string, options?: RequestOptions): Promise<null> {\n\t\treturn this.http.delete(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/\" + this.encodeId(id),\n\t\t\toptions,\n\t\t);\n\t}\n\t// ── Expand / Relation Fields ──\n\n\t/**\n\t * Get a list of expandable (relation) fields for this collection.\n\t * Useful for constructing `expand` query parameters.\n\t */\n\tasync expandFields(\n\t\toptions?: RequestOptions,\n\t): Promise<{ field: string; targetCollection: string }[] | null> {\n\t\tconst data = await this.http.get<{\n\t\t\tfields?: {\n\t\t\t\tname: string;\n\t\t\t\ttype: string;\n\t\t\t\toptions?: Record<string, string>;\n\t\t\t}[];\n\t\t}>(\"/collections/\" + this.encodeId(this.collectionName), options);\n\t\tif (!data?.fields) return null;\n\t\treturn data.fields\n\t\t\t.filter((f) => f.type === \"relation\" && f.options?.collection)\n\t\t\t.map((f) => ({\n\t\t\t\tfield: f.name,\n\t\t\t\ttargetCollection: f.options!.collection,\n\t\t\t}));\n\t}\n\n\t/**\n\t * Cast this service to a specific record shape.\n\t * Use when you have a hand-written or generated interface for the\n\t * collection and want compile-time checking of create/update/list.\n\t *\n\t * @example\n\t * ```ts\n\t * interface Post {\n\t * id: string;\n\t * title: string;\n\t * published: boolean;\n\t * }\n\t * const posts = client.collection(\"posts\").typed<Post>();\n\t * await posts.create({ title: \"Hi\", published: true }); // ✓\n\t * await posts.create({ nope: 1 }); // ✗ compile error\n\t * ```\n\t */\n\ttyped<TRecord = ApiRecord>(): CollectionService<TRecord> {\n\t\treturn this as unknown as CollectionService<TRecord>;\n\t}\n\n\t// ── Realtime Subscriptions (PocketBase-style) ──\n\n\t/**\n\t * Subscribe to realtime changes for this collection.\n\t * The event's `action` is one of `\"create\" | \"update\" | \"delete\"`.\n\t *\n\t * Access is governed by the collection's `listRule` (PocketBase semantics):\n\t * public collections allow anonymous subscriptions; other collections\n\t * require a matching logged-in user or superuser.\n\t *\n\t * @param callback Received on every record change.\n\t * @param recordId Optional — subscribe to a single record instead of `*`.\n\t * @returns A function that unsubscribes this callback.\n\t */\n\tsubscribe(callback: RealtimeCallback, recordId?: string): () => void {\n\t\tif (!this.realtime) {\n\t\t\tconsole.warn(\"[lazypock] No realtime service configured.\");\n\t\t\treturn () => {};\n\t\t}\n\t\tconst topic =\n\t\t\t\"collection:\" + this.collectionName + (recordId ? \":\" + recordId : \"\");\n\t\tconst handler = (raw: RealtimeEventLike) => {\n\t\t\tconst record = (raw.payload?.[\"record\"] ?? {}) as Record<string, unknown>;\n\t\t\tcallback({\n\t\t\t\taction: normalizeAction(raw.event, raw.payload?.[\"action\"]),\n\t\t\t\trecord,\n\t\t\t\ttopic: raw.topic,\n\t\t\t});\n\t\t};\n\t\tthis.realtime.ensureConnected();\n\t\tthis.realtime.subscribe(topic, handler as never);\n\t\treturn () => this.realtime?.unsubscribe(topic, handler as never);\n\t}\n\n\t/**\n\t * Unsubscribe all callbacks from this collection (or a specific record).\n\t * @param recordId Optional record id; omitting it unsubs everything.\n\t */\n\tunsubscribe(recordId?: string): void {\n\t\tconst topic =\n\t\t\t\"collection:\" + this.collectionName + (recordId ? \":\" + recordId : \"\");\n\t\tthis.realtime?.unsubscribe(topic);\n\t}\n\n\t// ── Auth Collection Methods ──\n\n\t/**\n\t * Authenticate with email/password against this auth collection.\n\t * Stores the returned token and user model in the auth store.\n\t */\n\tasync authWithPassword(\n\t\tidentity: string,\n\t\tpassword: string,\n\t\toptions?: RequestOptions,\n\t): Promise<\n\t\t({ token: string; record: ApiRecord } & Record<string, unknown>) | null\n\t> {\n\t\tconst data = await this.http.post<\n\t\t\t{ token: string; record: ApiRecord } & Record<string, unknown>\n\t\t>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/auth-with-password\",\n\t\t\t{ identity, password },\n\t\t\toptions,\n\t\t);\n\t\tif (data && this.authStore) {\n\t\t\tthis.authStore.setCollectionName(this.collectionName);\n\t\t\tthis.authStore.set(data.token, data.record as unknown as AuthModel);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * Refresh the auth token for the currently authenticated user.\n\t * Updates the stored token and user model.\n\t */\n\tasync authRefresh(\n\t\toptions?: RequestOptions,\n\t): Promise<\n\t\t({ token: string; record: ApiRecord } & Record<string, unknown>) | null\n\t> {\n\t\tconst data = await this.http.post<\n\t\t\t{ token: string; record: ApiRecord } & Record<string, unknown>\n\t\t>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/auth-refresh\",\n\t\t\tundefined,\n\t\t\toptions,\n\t\t);\n\t\tif (data && this.authStore) {\n\t\t\tthis.authStore.setCollectionName(this.collectionName);\n\t\t\tthis.authStore.set(data.token, data.record as unknown as AuthModel);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * Get available auth methods for this collection.\n\t */\n\t// ── end Realtime ──\n\n\tasync authMethods(\n\t\toptions?: RequestOptions,\n\t): Promise<Record<string, unknown> | null> {\n\t\treturn this.http.get<Record<string, unknown>>(\n\t\t\t\"/\" + this.encodeId(this.collectionName) + \"/auth-methods\",\n\t\t\toptions,\n\t\t);\n\t}\n}\n","// ── Phoenix Channel WebSocket Client ──────────────────\n//\n// Implements the Phoenix Channels protocol over WebSocket\n// to subscribe to collection realtime updates.\n//\n// Protocol: Phoenix V1 JSON Serializer (object-based messages).\n// Sends messages as JSON objects with {topic, event, payload, ref, join_ref}.\n// Receives messages as JSON objects with {topic, event, payload, ref}.\n\ninterface RealtimeEvent {\n\tevent: string;\n\ttopic: string;\n\tpayload: Record<string, unknown>;\n}\n\ninterface SubEntry {\n\ttopic: string;\n\tcallback: (e: RealtimeEvent) => void;\n}\n\nexport type RealtimeConnectOpts = {\n\t/** WebSocket URL (e.g. ws://localhost:4000/socket/websocket) */\n\turl: string;\n\t/** Auth token to pass as query param */\n\ttoken?: string;\n};\n\n/**\n * Derive a WebSocket URL from an HTTP base URL.\n * http://localhost:4000/api → ws://localhost:4000/socket/websocket\n */\nexport function wsUrlFromBaseUrl(baseUrl: string): string {\n\ttry {\n\t\tconst url = new URL(baseUrl);\n\t\tconst protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n\t\treturn `${protocol}//${url.host}/socket/websocket`;\n\t} catch {\n\t\treturn `${baseUrl.replace(/^http/, \"ws\").replace(/\\/api$/, \"\")}/socket/websocket`;\n\t}\n}\n\n/**\n * Phoenix Channel client for real-time collection subscriptions.\n *\n * Connects via WebSocket and subscribes to collection topics.\n * Includes automatic reconnection with exponential backoff.\n *\n * @example\n * ```ts\n * const rt = new RealtimeService();\n * rt.connect({ url: wsUrlFromBaseUrl('http://localhost:4000/api') });\n * rt.subscribe('collection:posts', (e) => console.log(e));\n * ```\n */\nexport class RealtimeService {\n\tprivate ws: WebSocket | null = null;\n\tprivate refCounter = 0;\n\tprivate subscriptions = new Map<string, SubEntry[]>();\n\tprivate reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate reconnectAttempt = 0;\n\tprivate maxReconnectDelay = 5000;\n\n\t// Callbacks for connection state\n\tonReconnect?: () => void;\n\tonDisconnect?: () => void;\n\tonError?: (err: Event) => void;\n\n\tprivate url: string = \"\";\n\tprivate token: string | undefined;\n\n\t/** Whether the WebSocket is currently open. */\n\tget isOpen(): boolean {\n\t\treturn this.ws?.readyState === WebSocket.OPEN;\n\t}\n\n\t/**\n\t * The most recently used WebSocket URL (set on {@link connect}).\n\t * Useful for SDK convenience methods that auto-connect before subscribing.\n\t */\n\tget lastUrl(): string {\n\t\treturn this.url;\n\t}\n\n\t/** The auth token configured for this connection (set on connect). */\n\tget lastToken(): string | undefined {\n\t\treturn this.token;\n\t}\n\n\t/**\n\t * Set the socket URL. Useful before subscribing so the SDK can\n\t * auto-connect on the first {@link subscribe}.\n\t */\n\tsetUrl(url: string): void {\n\t\tthis.url = url;\n\t}\n\n\t/*\n\t * Ensure the socket is connected, then subscribe.\n\t * Used by collection-level convenience wrappers so a connection is opened\n\t * automatically on the first subscribe (matching PocketBase behaviour —\n\t * works for anonymous/public collections too).\n\t */\n\tensureConnected(): void {\n\t\tif (this.isOpen || !this.url) return;\n\t\tif (typeof WebSocket === \"undefined\") return;\n\t\tthis.doConnect();\n\t}\n\n\tconnect(opts: RealtimeConnectOpts): void {\n\t\tthis.url = opts.url;\n\t\tthis.token = opts.token;\n\t\tthis.reconnectAttempt = 0;\n\t\tthis.doConnect();\n\t}\n\n\tdisconnect(): void {\n\t\tthis.clearReconnectTimer();\n\t\tthis.ws?.close();\n\t\tthis.ws = null;\n\t}\n\n\t/**\n\t * Subscribe to a topic (e.g. \"collection:posts\" or \"collection:posts:*\").\n\t * The backend Channel authorizes via listRule on join.\n\t */\n\tsubscribe(topic: string, callback: (e: RealtimeEvent) => void): void {\n\t\tconst subs = this.subscriptions.get(topic) || [];\n\t\tsubs.push({ topic, callback });\n\t\tthis.subscriptions.set(topic, subs);\n\n\t\tif (this.ws?.readyState === WebSocket.OPEN) {\n\t\t\tthis.joinTopic(topic);\n\t\t}\n\t}\n\n\t/**\n\t * Unsubscribe a specific callback from a topic.\n\t */\n\tunsubscribe(topic: string, callback?: (e: RealtimeEvent) => void): void {\n\t\tif (!callback) {\n\t\t\tthis.subscriptions.delete(topic);\n\t\t\treturn;\n\t\t}\n\t\tconst subs = this.subscriptions\n\t\t\t.get(topic)\n\t\t\t?.filter((s) => s.callback !== callback);\n\t\tif (subs && subs.length > 0) {\n\t\t\tthis.subscriptions.set(topic, subs);\n\t\t} else {\n\t\t\tthis.subscriptions.delete(topic);\n\t\t}\n\t}\n\n\tprivate resubscribeAll(): void {\n\t\tfor (const topic of this.subscriptions.keys()) {\n\t\t\tif (this.ws?.readyState === WebSocket.OPEN) {\n\t\t\t\tthis.joinTopic(topic);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate doConnect(): void {\n\t\tif (typeof WebSocket === \"undefined\") {\n\t\t\tconsole.warn(\n\t\t\t\t\"[lazypock] WebSocket not available — realtime subscriptions disabled\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tlet url = this.url;\n\t\tif (this.token) {\n\t\t\turl +=\n\t\t\t\t(url.includes(\"?\") ? \"&\" : \"?\") +\n\t\t\t\t\"token=\" +\n\t\t\t\tencodeURIComponent(this.token);\n\t\t}\n\n\t\tthis.ws = new WebSocket(url);\n\n\t\tthis.ws.onopen = () => {\n\t\t\tthis.reconnectAttempt = 0;\n\t\t\tthis.resubscribeAll();\n\t\t\tthis.startHeartbeat();\n\t\t};\n\n\t\tthis.ws.onmessage = (msg: MessageEvent) => {\n\t\t\tthis.handleMessage(msg.data);\n\t\t};\n\n\t\tthis.ws.onclose = () => {\n\t\t\tthis.stopHeartbeat();\n\t\t\tthis.onDisconnect?.();\n\t\t\tthis.scheduleReconnect();\n\t\t};\n\n\t\tthis.ws.onerror = (err: Event) => {\n\t\t\tthis.onError?.(err);\n\t\t};\n\t}\n\n\tprivate handleMessage(data: string): void {\n\t\tlet parsed: Record<string, unknown>;\n\t\ttry {\n\t\t\tparsed = JSON.parse(data) as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tif (typeof parsed !== \"object\" || !parsed.topic || !parsed.event) return;\n\n\t\tconst topic = parsed.topic as string;\n\t\tconst event = parsed.event as string;\n\t\tconst payload = (parsed.payload as Record<string, unknown>) || {};\n\n\t\t// Handle phx_reply (join/heartbeat responses)\n\t\tif (event === \"phx_reply\") return;\n\n\t\t// Relay incoming events to all subscribers of this topic\n\t\tconst subs = this.subscriptions.get(topic);\n\t\tif (subs) {\n\t\t\tconst e: RealtimeEvent = {\n\t\t\t\tevent,\n\t\t\t\ttopic,\n\t\t\t\tpayload: payload as Record<string, unknown>,\n\t\t\t};\n\t\t\tfor (const s of subs) {\n\t\t\t\ttry {\n\t\t\t\t\ts.callback(e);\n\t\t\t\t} catch {\n\t\t\t\t\t// swallow callback errors\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate joinTopic(topic: string): void {\n\t\tconst ref = this.nextRef();\n\t\t// Phoenix V1 JSON Serializer expects a JSON object, not an array\n\t\tconst msg = JSON.stringify({\n\t\t\ttopic: topic,\n\t\t\tevent: \"phx_join\",\n\t\t\tpayload: {},\n\t\t\tref: ref,\n\t\t});\n\t\tthis.ws?.send(msg);\n\t}\n\n\tprivate nextRef(): string {\n\t\tthis.refCounter++;\n\t\treturn this.refCounter.toString();\n\t}\n\n\t// ── Heartbeat ──\n\n\tprivate heartbeatInterval: ReturnType<typeof setInterval> | null = null;\n\n\tprivate startHeartbeat(): void {\n\t\tthis.stopHeartbeat();\n\t\tthis.heartbeatInterval = setInterval(() => {\n\t\t\tif (this.ws?.readyState === WebSocket.OPEN) {\n\t\t\t\tconst ref = this.nextRef();\n\t\t\t\t// Phoenix V1 JSON Serializer expects a JSON object\n\t\t\t\tconst msg = JSON.stringify({\n\t\t\t\t\ttopic: \"phoenix\",\n\t\t\t\t\tevent: \"heartbeat\",\n\t\t\t\t\tpayload: {},\n\t\t\t\t\tref: ref,\n\t\t\t\t});\n\t\t\t\tthis.ws.send(msg);\n\t\t\t}\n\t\t}, 30_000);\n\t}\n\n\tprivate stopHeartbeat(): void {\n\t\tif (this.heartbeatInterval) {\n\t\t\tclearInterval(this.heartbeatInterval);\n\t\t\tthis.heartbeatInterval = null;\n\t\t}\n\t}\n\n\t// ── Reconnect ──\n\n\tprivate scheduleReconnect(): void {\n\t\tthis.clearReconnectTimer();\n\t\tconst delay = Math.min(\n\t\t\t1000 * 2 ** this.reconnectAttempt,\n\t\t\tthis.maxReconnectDelay,\n\t\t);\n\t\tthis.reconnectAttempt++;\n\t\tthis.reconnectTimer = setTimeout(() => {\n\t\t\tthis.reconnectTimer = null;\n\t\t\tthis.onReconnect?.();\n\t\t\tthis.doConnect();\n\t\t}, delay);\n\t}\n\n\tprivate clearReconnectTimer(): void {\n\t\tif (this.reconnectTimer) {\n\t\t\tclearTimeout(this.reconnectTimer);\n\t\t\tthis.reconnectTimer = null;\n\t\t}\n\t}\n}\n","// ── File Service ─────────────────────────────────────────\n// Upload, download, and delete files.\n\nimport type { HttpClient } from \"./http\";\nimport type { RequestOptions } from \"./types\";\n\n/** Response shape from the server file endpoints */\nexport interface FileRecord {\n\tid: string;\n\tfilename: string;\n\tmimeType: string;\n\tsize: number;\n\turl: string;\n\t[key: string]: unknown;\n}\n\n/**\n * Construct a file URL from the API base URL and file ID.\n */\nexport function getFileUrl(baseUrl: string, fileId: string): string {\n\treturn baseUrl.replace(/\\/+$/, \"\") + \"/files/\" + encodeURIComponent(fileId);\n}\n\n/**\n * Service for file upload, retrieval, and deletion.\n * Access via {@link LazypockClient.files}.\n */\nexport class FilesService {\n\tconstructor(private http: HttpClient) {}\n\n\t/**\n\t * Upload a file or blob.\n\t *\n\t * @param file The File or Blob to upload.\n\t * @param filename Optional filename (required if `file` is a Blob without a name).\n\t * @param options Optional request options (signal, custom fetch).\n\t * @param meta Optional metadata: collectionName, recordId, fieldName for ownership tracking.\n\t */\n\tasync upload(\n\t\tfile: File | Blob,\n\t\tfilename?: string,\n\t\toptions?: RequestOptions,\n\t\tmeta?: { collectionName?: string; recordId?: string; fieldName?: string },\n\t): Promise<FileRecord | null> {\n\t\tif (typeof FormData === \"undefined\") {\n\t\t\tthrow new Error(\"FormData is not available in this environment\");\n\t\t}\n\n\t\tconst formData = new FormData();\n\t\tconst name = filename || (file instanceof File ? file.name : \"file\");\n\t\tformData.append(\"file\", file, name);\n\n\t\tif (meta?.collectionName)\n\t\t\tformData.append(\"collection_name\", meta.collectionName);\n\t\tif (meta?.recordId) formData.append(\"record_id\", meta.recordId);\n\t\tif (meta?.fieldName) formData.append(\"field_name\", meta.fieldName);\n\n\t\tconst data = await this.http.request<Record<string, unknown>>(\n\t\t\t\"POST\",\n\t\t\t\"/files\",\n\t\t\tformData,\n\t\t\toptions,\n\t\t);\n\n\t\treturn data as FileRecord | null;\n\t}\n\n\t/**\n\t * Fetch file metadata including URL.\n\t * @param fileId The file ID.\n\t */\n\tasync getUrl(fileId: string): Promise<string | null> {\n\t\tconst data = await this.http.request<Record<string, unknown>>(\n\t\t\t\"GET\",\n\t\t\t\"/files/\" + encodeURIComponent(fileId),\n\t\t);\n\t\tif (data && typeof data === \"object\" && \"url\" in data) {\n\t\t\treturn (data as Record<string, unknown>).url as string;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Delete a file by ID.\n\t * @param fileId The file ID.\n\t * @param options Optional request options.\n\t */\n\tasync delete(fileId: string, options?: RequestOptions): Promise<null> {\n\t\treturn this.http.request<null>(\n\t\t\t\"DELETE\",\n\t\t\t\"/files/\" + encodeURIComponent(fileId),\n\t\t\tundefined,\n\t\t\toptions,\n\t\t);\n\t}\n}\n","// ── LazypockClient — Root Client ───────────────────────\n// Extracted into its own module to avoid a circular import with\n// the typed-client factory (src/client.ts).\n\nimport { HttpClient } from \"./http\";\nimport {\n\tAuthStore,\n\tmemoryStorage,\n\ttype StorageAdapter,\n\ttype AuthModel,\n} from \"./auth\";\nimport { CollectionService } from \"./collection\";\nimport {\n\tApiError,\n\ttype ApiRecord,\n\ttype ListResult,\n\ttype RecordShape,\n\ttype CreateData,\n\ttype UpdateData,\n\ttype SystemFields,\n\ttype RequestOptions,\n} from \"./types\";\nimport { RealtimeService, wsUrlFromBaseUrl } from \"./realtime\";\nimport { FilesService, getFileUrl, type FileRecord } from \"./files\";\nimport type { CollectionSchema, SchemaField } from \"./schema\";\nimport { generateTypes, collectionTypeName } from \"./codegen\";\nimport { fieldTypeScriptType, fieldTypeKind, schemaFieldType } from \"./typegen\";\n\nexport {\n\tAuthStore,\n\tApiError,\n\tRealtimeService,\n\twsUrlFromBaseUrl,\n\tFilesService,\n\tgetFileUrl,\n\tCollectionService,\n\tgenerateTypes,\n\tcollectionTypeName,\n\tfieldTypeScriptType,\n\tfieldTypeKind,\n\tschemaFieldType,\n};\nexport type {\n\tStorageAdapter,\n\tAuthModel,\n\tApiRecord,\n\tListResult,\n\tRecordShape,\n\tCreateData,\n\tUpdateData,\n\tSystemFields,\n\tRequestOptions,\n\tFileRecord,\n\tCollectionSchema,\n\tSchemaField,\n};\n\n/** Options for constructing a {@link LazypockClient}. */\nexport interface LazypockClientOptions {\n\t/** API base URL (e.g. 'http://localhost:4000/api') */\n\tbaseUrl: string;\n\t/** Custom storage adapter (default: localStorage fallback) */\n\tstorage?: StorageAdapter;\n\t/** Explicit auth store instance (for sharing across modules) */\n\tauthStore?: AuthStore;\n\t/** Real-time service for Phoenix Channel WebSocket subscriptions */\n\trealtime?: RealtimeService;\n\t/**\n\t * Optional schema types for generating typed services at runtime.\n\t * When provided, `collection()` returns a service whose create/update\n\t * inputs are validated against the mapped field types.\n\t *\n\t * @experimental\n\t */\n\ttypes?: {\n\t\t/** Collection schemas fetched from the API (e.g. via `GET /collections`). */\n\t\tschemas?: CollectionSchema[];\n\t};\n}\n\n/**\n * Lazypock API client.\n *\n * Provides methods for authentication, CRUD operations on dynamic collections,\n * file management, and real-time subscriptions.\n *\n * @example\n * ```ts\n * const client = new LazypockClient({ baseUrl: 'http://localhost:4000/api' });\n * await client.login('admin@example.com', 'password');\n * const posts = await client.collection('posts').list();\n * ```\n */\nexport class LazypockClient {\n\treadonly http: HttpClient;\n\treadonly authStore: AuthStore;\n\treadonly realtime: RealtimeService;\n\treadonly files: FilesService;\n\tprivate collectionCache = new Map<string, CollectionService>();\n\tprivate schemaByName?: Map<string, CollectionSchema>;\n\n\t/**\n\t * Create a new Lazypock client.\n\t * @param options Configuration options.\n\t */\n\tconstructor(options: LazypockClientOptions) {\n\t\tconst baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n\t\tthis.authStore =\n\t\t\toptions.authStore ?? new AuthStore(options.storage ?? memoryStorage);\n\t\tthis.http = new HttpClient(baseUrl, this.authStore);\n\t\tthis.realtime = options.realtime ?? new RealtimeService();\n\t\t// Cache the socket URL so collection-level subscribe() can auto-connect.\n\t\tif (!options.realtime) {\n\t\t\tthis.realtime.setUrl(wsUrlFromBaseUrl(baseUrl));\n\t\t}\n\t\tthis.files = new FilesService(this.http);\n\t\tif (options.types?.schemas) {\n\t\t\tthis.schemaByName = new Map(\n\t\t\t\toptions.types.schemas.map((s) => [s.name, s]),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get or create a service for the given collection.\n\t * Services are cached after first access.\n\t *\n\t * For typed CRUD, either:\n\t * - cast at the call site: `client.collection(\"posts\").typed<Post>()`\n\t * - or use the typed factory: `createClient<{ posts: Post }>()`\n\t *\n\t * @param name The collection name.\n\t * @returns A {@link CollectionService} instance.\n\t */\n\tcollection(name: string): CollectionService<unknown> {\n\t\tlet svc = this.collectionCache.get(name);\n\t\tif (!svc) {\n\t\t\tsvc = new CollectionService(\n\t\t\t\tthis.http,\n\t\t\t\tname,\n\t\t\t\tthis.authStore,\n\t\t\t\tthis.realtime,\n\t\t\t);\n\t\t\tthis.collectionCache.set(name, svc);\n\t\t}\n\t\treturn svc;\n\t}\n\n\t/**\n\t * Get a typed service whose record shape is derived from the schema\n\t * passed via `options.types.schemas` (if available), or fall back to\n\t * the untyped service otherwise.\n\t *\n\t * @experimental\n\t */\n\tcollectionFor<TRecord = ApiRecord>(name: string): CollectionService<TRecord> {\n\t\treturn this.collection(name) as unknown as CollectionService<TRecord>;\n\t}\n\n\t/**\n\t * Generate TypeScript types from the schemas provided to this client\n\t * (via `options.types.schemas`). Returns a string ready to write to a\n\t * `lazypock.types.ts` file.\n\t */\n\tgenerateTypes(options?: { packageName?: string }): string {\n\t\tconst schemas = this.schemaByName ? [...this.schemaByName.values()] : [];\n\t\treturn generateTypes(schemas, options);\n\t}\n\n\t// ── Auth ──\n\n\t/** Check whether any superuser exists (for login vs setup screen routing). */\n\tasync checkSuperuser(): Promise<{ has_superuser: boolean } | null> {\n\t\treturn this.http.get<{ has_superuser: boolean }>(\"/superusers/check\");\n\t}\n\n\t/**\n\t * Create the initial superuser account.\n\t * Only works when no superuser exists yet.\n\t * Stores the returned token in the auth store.\n\t * @param email Superuser email.\n\t * @param password Superuser password (min 8 chars).\n\t */\n\tasync setup(\n\t\temail: string,\n\t\tpassword: string,\n\t): Promise<({ token: string } & Record<string, unknown>) | null> {\n\t\tconst data = await this.http.post<\n\t\t\t{ token: string } & Record<string, unknown>\n\t\t>(\"/superusers/setup\", { email, password });\n\t\tif (data) {\n\t\t\tthis.authStore.setCollectionName(null);\n\t\t\tthis.authStore.set(data.token, null);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * Authenticate as a superuser or auth collection user.\n\t *\n\t * When `collection` is provided, authenticates against\n\t * `/{collection}/auth-with-password`. Otherwise logs in as superuser.\n\t * Stores the returned token in the auth store.\n\t *\n\t * @param email User email or identity.\n\t * @param password User password.\n\t * @param collection Optional auth collection name.\n\t */\n\tasync login(\n\t\temail: string,\n\t\tpassword: string,\n\t\tcollection?: string,\n\t): Promise<({ token: string } & Record<string, unknown>) | null> {\n\t\tlet data;\n\t\tif (collection) {\n\t\t\tdata = await this.http.post<\n\t\t\t\t{ token: string; record: Record<string, unknown> } & Record<\n\t\t\t\t\tstring,\n\t\t\t\t\tunknown\n\t\t\t\t>\n\t\t\t>(\"/\" + encodeURIComponent(collection) + \"/auth-with-password\", {\n\t\t\t\tidentity: email,\n\t\t\t\tpassword,\n\t\t\t});\n\t\t\tif (data && data.record) {\n\t\t\t\tthis.authStore.setCollectionName(collection);\n\t\t\t\tthis.authStore.set(data.token, data.record as unknown as AuthModel);\n\t\t\t}\n\t\t} else {\n\t\t\tdata = await this.http.post<{ token: string } & Record<string, unknown>>(\n\t\t\t\t\"/superusers/login\",\n\t\t\t\t{ email, password },\n\t\t\t);\n\t\t\tif (data) {\n\t\t\t\tthis.authStore.setCollectionName(null);\n\t\t\t\tthis.authStore.set(data.token, null);\n\t\t\t}\n\t\t}\n\t\treturn data;\n\t}\n\n\t/** Fetch the current superuser profile and refresh the auth model. */\n\tasync me<T = ApiRecord>(options?: RequestOptions): Promise<T | null> {\n\t\tconst data = await this.http.get<T>(\"/superusers/me\", options);\n\t\tif (data) {\n\t\t\t// Update the auth model with fresh data\n\t\t\tthis.authStore.set(this.authStore.token, data as unknown as AuthModel);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * Authenticate against an auth collection with email/password.\n\t * Stores the returned token and user record in the auth store.\n\t *\n\t * @param collection The auth collection name.\n\t * @param identity Email or username.\n\t * @param password Password.\n\t * @param options Optional request options.\n\t */\n\tasync authWithPassword(\n\t\tcollection: string,\n\t\tidentity: string,\n\t\tpassword: string,\n\t\toptions?: RequestOptions,\n\t): Promise<\n\t\t({ token: string; record: ApiRecord } & Record<string, unknown>) | null\n\t> {\n\t\tconst data = await this.http.post<\n\t\t\t{ token: string; record: ApiRecord } & Record<string, unknown>\n\t\t>(\n\t\t\t\"/\" + encodeURIComponent(collection) + \"/auth-with-password\",\n\t\t\t{ identity, password },\n\t\t\toptions,\n\t\t);\n\t\tif (data) {\n\t\t\tthis.authStore.setCollectionName(collection);\n\t\t\tthis.authStore.set(data.token, data.record as unknown as AuthModel);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * Refresh an auth collection token.\n\t * Uses the currently stored auth token.\n\t *\n\t * @param collection The auth collection name.\n\t * @param options Optional request options.\n\t */\n\tasync authRefresh(\n\t\tcollection: string,\n\t\toptions?: RequestOptions,\n\t): Promise<\n\t\t({ token: string; record: ApiRecord } & Record<string, unknown>) | null\n\t> {\n\t\tconst data = await this.http.post<\n\t\t\t{ token: string; record: ApiRecord } & Record<string, unknown>\n\t\t>(\n\t\t\t\"/\" + encodeURIComponent(collection) + \"/auth-refresh\",\n\t\t\tundefined,\n\t\t\toptions,\n\t\t);\n\t\tif (data) {\n\t\t\tthis.authStore.setCollectionName(collection);\n\t\t\tthis.authStore.set(data.token, data.record as unknown as AuthModel);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/** Clear the current auth state and remove persisted tokens. */\n\tlogout(): void {\n\t\tthis.authStore.clear();\n\t}\n\n\t// ── Health ──\n\n\t/** Ping the API health endpoint. */\n\thealth(options?: RequestOptions): Promise<Record<string, unknown> | null> {\n\t\treturn this.http.get<Record<string, unknown>>(\"/health\", options);\n\t}\n\n\t// ── Collection Management (admin) ──\n\n\t/**\n\t * List all collections (admin).\n\t * @param q URL query string (e.g. `page=1&perPage=200`).\n\t * @param options Optional request options.\n\t */\n\tlistCollections(\n\t\tq?: string,\n\t\toptions?: RequestOptions,\n\t): Promise<ListResult<ApiRecord> | null> {\n\t\treturn this.http.get<ListResult<ApiRecord>>(\n\t\t\t\"/collections\" + (q ? \"?\" + q : \"\"),\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Get a single collection by ID or name.\n\t * @param id Collection ID or name.\n\t * @param options Optional request options.\n\t */\n\tgetCollection(\n\t\tid: string,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.get<ApiRecord>(\n\t\t\t\"/collections/\" + encodeURIComponent(id),\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Create a new collection (admin).\n\t * @param data Collection definition (name, type, fields, options, rules, etc.).\n\t * @param options Optional request options.\n\t */\n\tcreateCollection(\n\t\tdata: Record<string, unknown>,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.post<ApiRecord>(\"/collections\", data, options);\n\t}\n\n\t/**\n\t * Update an existing collection (admin).\n\t * @param id Collection ID or name.\n\t * @param data Updated collection fields.\n\t * @param options Optional request options.\n\t */\n\tupdateCollection(\n\t\tid: string,\n\t\tdata: Record<string, unknown>,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.patch<ApiRecord>(\n\t\t\t\"/collections/\" + encodeURIComponent(id),\n\t\t\tdata,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Delete a collection (admin).\n\t * @param id Collection ID or name.\n\t * @param options Optional request options.\n\t */\n\tdeleteCollection(id: string, options?: RequestOptions): Promise<null> {\n\t\treturn this.http.delete(\"/collections/\" + encodeURIComponent(id), options);\n\t}\n\n\t// ── Records (dynamic collection) ──\n\n\t/**\n\t * List records from a dynamic collection with optional filter/sort/pagination.\n\t *\n\t * @param coll Collection name.\n\t * @param params Query parameters including:\n\t * - `filter` — PocketBase filter syntax (e.g. `title~'hello' && published=true`)\n\t * - `sort` — Comma-separated, `-` prefix for DESC (e.g. `-created,title`)\n\t * - `page` — Page number (default: 1)\n\t * - `perPage` — Items per page (default: 30, max: 200)\n\t * - `expand` — Comma-separated relation fields (e.g. `author,category`)\n\t * @param options Optional request options.\n\t */\n\tlistRecords(\n\t\tcoll: string,\n\t\tparams?: Record<string, string>,\n\t\toptions?: RequestOptions,\n\t): Promise<ListResult<ApiRecord> | null> {\n\t\tconst qs = params ? \"?\" + new URLSearchParams(params).toString() : \"\";\n\t\treturn this.http.get<ListResult<ApiRecord>>(\n\t\t\t\"/\" + encodeURIComponent(coll) + qs,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Get a single record by ID.\n\t * @param coll Collection name.\n\t * @param id Record ID.\n\t * @param options Optional request options.\n\t */\n\tgetRecord(\n\t\tcoll: string,\n\t\tid: string,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.get<ApiRecord>(\n\t\t\t\"/\" + encodeURIComponent(coll) + \"/\" + encodeURIComponent(id),\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Create a record in a dynamic collection.\n\t * @param coll Collection name.\n\t * @param data Record fields.\n\t * @param options Optional request options.\n\t */\n\tcreateRecord(\n\t\tcoll: string,\n\t\tdata: Record<string, unknown>,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.post<ApiRecord>(\n\t\t\t\"/\" + encodeURIComponent(coll),\n\t\t\tdata,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Update a record in a dynamic collection.\n\t * @param coll Collection name.\n\t * @param id Record ID.\n\t * @param data Updated record fields.\n\t * @param options Optional request options.\n\t */\n\tupdateRecord(\n\t\tcoll: string,\n\t\tid: string,\n\t\tdata: Record<string, unknown>,\n\t\toptions?: RequestOptions,\n\t): Promise<ApiRecord | null> {\n\t\treturn this.http.patch<ApiRecord>(\n\t\t\t\"/\" + encodeURIComponent(coll) + \"/\" + encodeURIComponent(id),\n\t\t\tdata,\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/**\n\t * Delete a record from a dynamic collection.\n\t * @param coll Collection name.\n\t * @param id Record ID.\n\t * @param options Optional request options.\n\t */\n\tdeleteRecord(\n\t\tcoll: string,\n\t\tid: string,\n\t\toptions?: RequestOptions,\n\t): Promise<null> {\n\t\treturn this.http.delete(\n\t\t\t\"/\" + encodeURIComponent(coll) + \"/\" + encodeURIComponent(id),\n\t\t\toptions,\n\t\t);\n\t}\n}\n","// ── Typed client factory ────────────────────────────────\n// Generic over a collections map, so collection access is type-checked\n// without hand-writing interfaces — pair with the codegen CLI output.\n\nimport { LazypockClient } from \"./lazypock\";\nimport type { CollectionService } from \"./collection\";\nimport type { LazypockClientOptions } from \"./lazypock\";\n\n/**\n * A collections map: `{ posts: PostRecord; users: UserRecord; ... }`.\n * Generated by the codegen CLI (`npx lazypock-gen`) or written by hand.\n */\nexport type LazypockCollections = Record<string, unknown>;\n\n/**\n * Client typed against a {@link LazypockCollections} map.\n *\n * ```ts\n * import { createClient } from \"lazypock/generated\";\n * const client = createClient({ baseUrl: \"http://localhost:4000/api\" });\n * const posts = await client.collection(\"posts\").list<PostsRecord>();\n * ```\n */\nexport class TypedClient<\n\tTCollections extends LazypockCollections = LazypockCollections,\n> extends LazypockClient {\n\t/**\n\t * Get a typed service for a collection.\n\t * When `TCollections` is provided, unknown collection names are rejected.\n\t * @typeParam K — Collection name (keyof TCollections).\n\t */\n\toverride collection<K extends keyof TCollections>(\n\t\tname: K,\n\t): CollectionService<TCollections[K]>;\n\toverride collection(name: string): CollectionService<unknown> {\n\t\treturn super.collection(name) as unknown as CollectionService<unknown>;\n\t}\n}\n\n/**\n * Create a {@link TypedClient}.\n * @typeParam TCollections — Map of collection name → record shape.\n */\nexport function createClient<\n\tTCollections extends LazypockCollections = LazypockCollections,\n>(options: LazypockClientOptions): TypedClient<TCollections> {\n\treturn new TypedClient<TCollections>(options);\n}\n"],"mappings":";;;;;;;;;AAqEO,IAAM,WAAN,cAAuB,MAAM;AAAA,EAInC,YAAY,SAAiB,MAAe,QAAgB;AAC3D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EACf;AACD;;;ACrEO,IAAM,aAAN,MAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,EASvB,YAAY,SAAiB,WAAsB;AAClD,SAAK,UAAU,QAAQ,QAAQ,QAAQ,EAAE;AACzC,SAAK,YAAY;AACjB,SAAK,eAAe,WAAW,MAAM,KAAK,UAAU;AAAA,EACrD;AAAA,EAEA,MAAc,cAGJ;AACT,UAAM,aAAa,KAAK,UAAU;AAClC,QAAI,CAAC,WAAY,QAAO;AACxB,QAAI;AACH,YAAM,MACL,KAAK,UAAU,MAAM,mBAAmB,UAAU,IAAI;AACvD,YAAM,UAAkC;AAAA,QACvC,gBAAgB;AAAA,MACjB;AACA,UAAI,KAAK,UAAU,OAAO;AACzB,gBAAQ,eAAe,IAAI,YAAY,KAAK,UAAU;AAAA,MACvD;AACA,YAAM,MAAM,MAAM,KAAK,aAAa,KAAK;AAAA,QACxC,QAAQ;AAAA,QACR;AAAA,MACD,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACZ,aAAK,UAAU,MAAM;AACrB,eAAO;AAAA,MACR;AACA,YAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAI,QAAQ,OAAO,KAAK,UAAU,UAAU;AAC3C,aAAK,UAAU;AAAA,UACd,KAAK;AAAA,UACJ,KAAK,UAA6C;AAAA,QACpD;AACA,eAAO;AAAA,MACR;AACA,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,QACL,QACA,MACA,MACA,SACoB;AAEpB,QAAI,KAAK,UAAU,aAAa,KAAK,UAAU,gBAAgB;AAC9D,YAAM,KAAK,YAAY;AAAA,IACxB;AAEA,QAAI,MAAM,KAAK,UAAU;AACzB,QAAI,SAAS,QAAQ;AACpB,YAAM,KAAK,IAAI,gBAAgB,QAAQ,MAAM,EAAE,SAAS;AACxD,UAAI,IAAI;AACP,gBAAQ,KAAK,SAAS,GAAG,IAAI,MAAM,OAAO;AAAA,MAC3C;AAAA,IACD;AACA,UAAM,UAAkC;AAAA,MACvC,GAAG,SAAS;AAAA,IACb;AAGA,QAAI,EAAE,gBAAgB,WAAW;AAChC,cAAQ,cAAc,IAAI;AAAA,IAC3B;AAEA,QAAI,KAAK,UAAU,OAAO;AACzB,cAAQ,eAAe,IAAI,YAAY,KAAK,UAAU;AAAA,IACvD;AAEA,UAAM,OAAoB;AAAA,MACzB;AAAA,MACA;AAAA,MACA,QAAQ,SAAS;AAAA,IAClB;AAEA,QAAI,QAAQ,QAAQ,WAAW,SAAS,WAAW,UAAU;AAC5D,UAAI,gBAAgB,UAAU;AAC7B,aAAK,OAAO;AAAA,MACb,OAAO;AACN,aAAK,OAAO,KAAK,UAAU,IAAI;AAAA,MAChC;AAAA,IACD;AAEA,UAAM,UAAU,SAAS,SAAS,KAAK;AACvC,UAAM,MAAM,MAAM,QAAQ,KAAK,IAAI;AAEnC,QAAI,IAAI,WAAW,IAAK,QAAO;AAG/B,QAAI,WAAW;AACf,QAAI,OAAgC,CAAC;AACrC,QAAI;AACH,iBAAW,MAAM,IAAI,KAAK;AAC1B,UAAI,UAAU;AACb,eAAO,KAAK,MAAM,QAAQ;AAAA,MAC3B;AAAA,IACD,QAAQ;AAAA,IAER;AAEA,QAAI,CAAC,IAAI,IAAI;AACZ,YAAM,IAAI;AAAA,SACR,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,IAAI,eACtD,8BAA8B,IAAI,MAAM;AAAA,QACzC;AAAA,QACA,IAAI;AAAA,MACL;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAiB,MAAc,SAA6C;AAC3E,WAAO,KAAK,QAAW,OAAO,MAAM,QAAW,OAAO;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,KACC,MACA,MACA,SACoB;AACpB,WAAO,KAAK,QAAW,QAAQ,MAAM,MAAM,OAAO;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MACC,MACA,MACA,SACoB;AACpB,WAAO,KAAK,QAAW,SAAS,MAAM,MAAM,OAAO;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OACC,MACA,SACoB;AACpB,WAAO,KAAK,QAAW,UAAU,MAAM,QAAW,OAAO;AAAA,EAC1D;AACD;;;ACxKA,IAAM,eAAe,IAAI,KAAK,KAAK,KAAK;AAEjC,IAAM,YAAN,MAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,EAYtB,YAAY,SAA0B;AAXtC,SAAQ,SAAS;AACjB,SAAQ,SAA2B;AACnC,SAAQ,kBAAiC;AACzC,SAAQ,kBAAiC;AACzC,SAAQ,YAAY,oBAAI,IAAkB;AAQzC,SAAK,UAAU,WAAW;AAAA,MACzB,KAAK,CAAC,SAAiB;AAAA,MACvB,KAAK,MAAM;AAAA,MAAC;AAAA,MACZ,QAAQ,MAAM;AAAA,MAAC;AAAA,IAChB;AAAA,EACD;AAAA;AAAA,EAGA,IAAI,QAAgB;AACnB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,QAA0B;AAC7B,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,UAAmB;AACtB,WAAO,CAAC,CAAC,KAAK;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,YAAqB;AACxB,WACC,KAAK,oBAAoB,QACzB,KAAK,IAAI,KAAK,KAAK,kBAAkB;AAAA,EAEvC;AAAA;AAAA,EAGA,IAAI,iBAAgC;AACnC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB,MAA2B;AAC5C,SAAK,kBAAkB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAsB;AAC3B,UAAM,CAAC,OAAO,OAAO,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,MACnD,KAAK,QAAQ,IAAI,YAAY;AAAA,MAC7B,KAAK,QAAQ,IAAI,YAAY;AAAA,MAC7B,KAAK,QAAQ,IAAI,iBAAiB;AAAA,IACnC,CAAC;AACD,QAAI,MAAO,MAAK,SAAS;AACzB,QAAI,UAAW,MAAK,kBAAkB,SAAS,WAAW,EAAE,KAAK;AACjE,QAAI,OAAO;AACV,UAAI;AACH,aAAK,SAAS,KAAK,MAAM,KAAK;AAAA,MAC/B,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,OAAe,OAA+B;AACjD,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,kBAAkB,KAAK,IAAI,IAAI;AACpC,SAAK,QAAQ,IAAI;AAAA,MAChB,KAAK,QAAQ,IAAI,cAAc,KAAK;AAAA,MACpC,KAAK,QAAQ,IAAI,mBAAmB,OAAO,KAAK,eAAe,CAAC;AAAA,MAChE,QACG,KAAK,QAAQ,IAAI,cAAc,KAAK,UAAU,KAAK,CAAC,IACpD,KAAK,QAAQ,OAAO,YAAY;AAAA,IACpC,CAAC;AACD,SAAK,OAAO;AAAA,EACb;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACb,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,kBAAkB;AACvB,SAAK,kBAAkB;AACvB,SAAK,QAAQ,IAAI;AAAA,MAChB,KAAK,QAAQ,OAAO,YAAY;AAAA,MAChC,KAAK,QAAQ,OAAO,iBAAiB;AAAA,MACrC,KAAK,QAAQ,OAAO,YAAY;AAAA,IACjC,CAAC;AACD,SAAK,OAAO;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,IAA8B;AACtC,SAAK,UAAU,IAAI,EAAE;AACrB,WAAO,MAAM,KAAK,UAAU,OAAO,EAAE;AAAA,EACtC;AAAA,EAEQ,SAAe;AACtB,eAAW,MAAM,KAAK,WAAW;AAChC,SAAG,KAAK,QAAQ,KAAK,MAAM;AAAA,IAC5B;AAAA,EACD;AACD;AAIO,IAAM,gBAAgC;AAAA,EAC5C,IAAI,KAAa;AAChB,QAAI;AACH,aAAO,aAAa,QAAQ,GAAG;AAAA,IAChC,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EACA,IAAI,KAAa,OAAe;AAC/B,QAAI;AACH,mBAAa,QAAQ,KAAK,KAAK;AAAA,IAChC,QAAQ;AAAA,IAER;AAAA,EACD;AAAA,EACA,OAAO,KAAa;AACnB,QAAI;AACH,mBAAa,WAAW,GAAG;AAAA,IAC5B,QAAQ;AAAA,IAER;AAAA,EACD;AACD;;;ACrJA,SAAS,gBACR,OACA,WAC4B;AAC5B,MAAI,OAAO,cAAc,UAAU;AAClC,UAAM,IAAI,UAAU,YAAY;AAChC,QAAI,MAAM,YAAY,MAAM,YAAY,MAAM,UAAU;AACvD,aAAO;AAAA,IACR;AAAA,EACD;AACA,MAAI,UAAU,gBAAiB,QAAO;AACtC,MAAI,UAAU,YAAY,UAAU,gBAAiB,QAAO;AAC5D,MAAI,UAAU,YAAY,UAAU,gBAAiB,QAAO;AAC5D,SAAO;AACR;AAQO,IAAM,oBAAN,MAAuC;AAAA;AAAA,EAO7C,YACC,MACA,gBACA,WACA,UACC;AACD,SAAK,OAAO;AACZ,SAAK,iBAAiB;AACtB,SAAK,YAAY;AACjB,SAAK,WAAW;AAAA,EACjB;AAAA,EAEQ,SAAS,IAAoB;AACpC,WAAO,mBAAmB,EAAE;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KACC,QACA,SACgC;AAChC,UAAM,KAAK,SAAS,MAAM,IAAI,gBAAgB,MAAM,EAAE,SAAS,IAAI;AACnE,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,KAAK,SAAS,KAAK,cAAc,IAAI;AAAA,MAC3C;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,IAAY,SAA6C;AAC/D,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,KAAK,SAAS,KAAK,cAAc,IAAI,MAAM,KAAK,SAAS,EAAE;AAAA,MACjE;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OACC,MACA,SACoB;AACpB,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,KAAK,SAAS,KAAK,cAAc;AAAA,MACvC;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OACC,IACA,MACA,SACoB;AACpB,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,KAAK,SAAS,KAAK,cAAc,IAAI,MAAM,KAAK,SAAS,EAAE;AAAA,MACjE;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,IAAY,SAAyC;AAC3D,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,KAAK,SAAS,KAAK,cAAc,IAAI,MAAM,KAAK,SAAS,EAAE;AAAA,MACjE;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aACL,SACgE;AAChE,UAAM,OAAO,MAAM,KAAK,KAAK,IAM1B,kBAAkB,KAAK,SAAS,KAAK,cAAc,GAAG,OAAO;AAChE,QAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,WAAO,KAAK,OACV,OAAO,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,SAAS,UAAU,EAC5D,IAAI,CAAC,OAAO;AAAA,MACZ,OAAO,EAAE;AAAA,MACT,kBAAkB,EAAE,QAAS;AAAA,IAC9B,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,QAAyD;AACxD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,UAAU,UAA4B,UAA+B;AACpE,QAAI,CAAC,KAAK,UAAU;AACnB,cAAQ,KAAK,4CAA4C;AACzD,aAAO,MAAM;AAAA,MAAC;AAAA,IACf;AACA,UAAM,QACL,gBAAgB,KAAK,kBAAkB,WAAW,MAAM,WAAW;AACpE,UAAM,UAAU,CAAC,QAA2B;AAC3C,YAAM,SAAU,IAAI,UAAU,QAAQ,KAAK,CAAC;AAC5C,eAAS;AAAA,QACR,QAAQ,gBAAgB,IAAI,OAAO,IAAI,UAAU,QAAQ,CAAC;AAAA,QAC1D;AAAA,QACA,OAAO,IAAI;AAAA,MACZ,CAAC;AAAA,IACF;AACA,SAAK,SAAS,gBAAgB;AAC9B,SAAK,SAAS,UAAU,OAAO,OAAgB;AAC/C,WAAO,MAAM,KAAK,UAAU,YAAY,OAAO,OAAgB;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,UAAyB;AACpC,UAAM,QACL,gBAAgB,KAAK,kBAAkB,WAAW,MAAM,WAAW;AACpE,SAAK,UAAU,YAAY,KAAK;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBACL,UACA,UACA,SAGC;AACD,UAAM,OAAO,MAAM,KAAK,KAAK;AAAA,MAG5B,MAAM,KAAK,SAAS,KAAK,cAAc,IAAI;AAAA,MAC3C,EAAE,UAAU,SAAS;AAAA,MACrB;AAAA,IACD;AACA,QAAI,QAAQ,KAAK,WAAW;AAC3B,WAAK,UAAU,kBAAkB,KAAK,cAAc;AACpD,WAAK,UAAU,IAAI,KAAK,OAAO,KAAK,MAA8B;AAAA,IACnE;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YACL,SAGC;AACD,UAAM,OAAO,MAAM,KAAK,KAAK;AAAA,MAG5B,MAAM,KAAK,SAAS,KAAK,cAAc,IAAI;AAAA,MAC3C;AAAA,MACA;AAAA,IACD;AACA,QAAI,QAAQ,KAAK,WAAW;AAC3B,WAAK,UAAU,kBAAkB,KAAK,cAAc;AACpD,WAAK,UAAU,IAAI,KAAK,OAAO,KAAK,MAA8B;AAAA,IACnE;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YACL,SAC0C;AAC1C,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,KAAK,SAAS,KAAK,cAAc,IAAI;AAAA,MAC3C;AAAA,IACD;AAAA,EACD;AACD;;;ACrRO,SAAS,iBAAiB,SAAyB;AACzD,MAAI;AACH,UAAM,MAAM,IAAI,IAAI,OAAO;AAC3B,UAAM,WAAW,IAAI,aAAa,WAAW,SAAS;AACtD,WAAO,GAAG,QAAQ,KAAK,IAAI,IAAI;AAAA,EAChC,QAAQ;AACP,WAAO,GAAG,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ,UAAU,EAAE,CAAC;AAAA,EAC/D;AACD;AAeO,IAAM,kBAAN,MAAsB;AAAA,EAAtB;AACN,SAAQ,KAAuB;AAC/B,SAAQ,aAAa;AACrB,SAAQ,gBAAgB,oBAAI,IAAwB;AACpD,SAAQ,iBAAuD;AAC/D,SAAQ,mBAAmB;AAC3B,SAAQ,oBAAoB;AAO5B,SAAQ,MAAc;AA0LtB;AAAA,SAAQ,oBAA2D;AAAA;AAAA;AAAA,EAtLnE,IAAI,SAAkB;AACrB,WAAO,KAAK,IAAI,eAAe,UAAU;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,UAAkB;AACrB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,IAAI,YAAgC;AACnC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,KAAmB;AACzB,SAAK,MAAM;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAwB;AACvB,QAAI,KAAK,UAAU,CAAC,KAAK,IAAK;AAC9B,QAAI,OAAO,cAAc,YAAa;AACtC,SAAK,UAAU;AAAA,EAChB;AAAA,EAEA,QAAQ,MAAiC;AACxC,SAAK,MAAM,KAAK;AAChB,SAAK,QAAQ,KAAK;AAClB,SAAK,mBAAmB;AACxB,SAAK,UAAU;AAAA,EAChB;AAAA,EAEA,aAAmB;AAClB,SAAK,oBAAoB;AACzB,SAAK,IAAI,MAAM;AACf,SAAK,KAAK;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,OAAe,UAA4C;AACpE,UAAM,OAAO,KAAK,cAAc,IAAI,KAAK,KAAK,CAAC;AAC/C,SAAK,KAAK,EAAE,OAAO,SAAS,CAAC;AAC7B,SAAK,cAAc,IAAI,OAAO,IAAI;AAElC,QAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AAC3C,WAAK,UAAU,KAAK;AAAA,IACrB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,OAAe,UAA6C;AACvE,QAAI,CAAC,UAAU;AACd,WAAK,cAAc,OAAO,KAAK;AAC/B;AAAA,IACD;AACA,UAAM,OAAO,KAAK,cAChB,IAAI,KAAK,GACR,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ;AACxC,QAAI,QAAQ,KAAK,SAAS,GAAG;AAC5B,WAAK,cAAc,IAAI,OAAO,IAAI;AAAA,IACnC,OAAO;AACN,WAAK,cAAc,OAAO,KAAK;AAAA,IAChC;AAAA,EACD;AAAA,EAEQ,iBAAuB;AAC9B,eAAW,SAAS,KAAK,cAAc,KAAK,GAAG;AAC9C,UAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AAC3C,aAAK,UAAU,KAAK;AAAA,MACrB;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,YAAkB;AACzB,QAAI,OAAO,cAAc,aAAa;AACrC,cAAQ;AAAA,QACP;AAAA,MACD;AACA;AAAA,IACD;AAEA,QAAI,MAAM,KAAK;AACf,QAAI,KAAK,OAAO;AACf,cACE,IAAI,SAAS,GAAG,IAAI,MAAM,OAC3B,WACA,mBAAmB,KAAK,KAAK;AAAA,IAC/B;AAEA,SAAK,KAAK,IAAI,UAAU,GAAG;AAE3B,SAAK,GAAG,SAAS,MAAM;AACtB,WAAK,mBAAmB;AACxB,WAAK,eAAe;AACpB,WAAK,eAAe;AAAA,IACrB;AAEA,SAAK,GAAG,YAAY,CAAC,QAAsB;AAC1C,WAAK,cAAc,IAAI,IAAI;AAAA,IAC5B;AAEA,SAAK,GAAG,UAAU,MAAM;AACvB,WAAK,cAAc;AACnB,WAAK,eAAe;AACpB,WAAK,kBAAkB;AAAA,IACxB;AAEA,SAAK,GAAG,UAAU,CAAC,QAAe;AACjC,WAAK,UAAU,GAAG;AAAA,IACnB;AAAA,EACD;AAAA,EAEQ,cAAc,MAAoB;AACzC,QAAI;AACJ,QAAI;AACH,eAAS,KAAK,MAAM,IAAI;AAAA,IACzB,QAAQ;AACP;AAAA,IACD;AACA,QAAI,OAAO,WAAW,YAAY,CAAC,OAAO,SAAS,CAAC,OAAO,MAAO;AAElE,UAAM,QAAQ,OAAO;AACrB,UAAM,QAAQ,OAAO;AACrB,UAAM,UAAW,OAAO,WAAuC,CAAC;AAGhE,QAAI,UAAU,YAAa;AAG3B,UAAM,OAAO,KAAK,cAAc,IAAI,KAAK;AACzC,QAAI,MAAM;AACT,YAAM,IAAmB;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,MACD;AACA,iBAAW,KAAK,MAAM;AACrB,YAAI;AACH,YAAE,SAAS,CAAC;AAAA,QACb,QAAQ;AAAA,QAER;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,UAAU,OAAqB;AACtC,UAAM,MAAM,KAAK,QAAQ;AAEzB,UAAM,MAAM,KAAK,UAAU;AAAA,MAC1B;AAAA,MACA,OAAO;AAAA,MACP,SAAS,CAAC;AAAA,MACV;AAAA,IACD,CAAC;AACD,SAAK,IAAI,KAAK,GAAG;AAAA,EAClB;AAAA,EAEQ,UAAkB;AACzB,SAAK;AACL,WAAO,KAAK,WAAW,SAAS;AAAA,EACjC;AAAA,EAMQ,iBAAuB;AAC9B,SAAK,cAAc;AACnB,SAAK,oBAAoB,YAAY,MAAM;AAC1C,UAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AAC3C,cAAM,MAAM,KAAK,QAAQ;AAEzB,cAAM,MAAM,KAAK,UAAU;AAAA,UAC1B,OAAO;AAAA,UACP,OAAO;AAAA,UACP,SAAS,CAAC;AAAA,UACV;AAAA,QACD,CAAC;AACD,aAAK,GAAG,KAAK,GAAG;AAAA,MACjB;AAAA,IACD,GAAG,GAAM;AAAA,EACV;AAAA,EAEQ,gBAAsB;AAC7B,QAAI,KAAK,mBAAmB;AAC3B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC1B;AAAA,EACD;AAAA;AAAA,EAIQ,oBAA0B;AACjC,SAAK,oBAAoB;AACzB,UAAM,QAAQ,KAAK;AAAA,MAClB,MAAO,KAAK,KAAK;AAAA,MACjB,KAAK;AAAA,IACN;AACA,SAAK;AACL,SAAK,iBAAiB,WAAW,MAAM;AACtC,WAAK,iBAAiB;AACtB,WAAK,cAAc;AACnB,WAAK,UAAU;AAAA,IAChB,GAAG,KAAK;AAAA,EACT;AAAA,EAEQ,sBAA4B;AACnC,QAAI,KAAK,gBAAgB;AACxB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACvB;AAAA,EACD;AACD;;;AC1RO,SAAS,WAAW,SAAiB,QAAwB;AACnE,SAAO,QAAQ,QAAQ,QAAQ,EAAE,IAAI,YAAY,mBAAmB,MAAM;AAC3E;AAMO,IAAM,eAAN,MAAmB;AAAA,EACzB,YAAoB,MAAkB;AAAlB;AAAA,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUvC,MAAM,OACL,MACA,UACA,SACA,MAC6B;AAC7B,QAAI,OAAO,aAAa,aAAa;AACpC,YAAM,IAAI,MAAM,+CAA+C;AAAA,IAChE;AAEA,UAAM,WAAW,IAAI,SAAS;AAC9B,UAAM,OAAO,aAAa,gBAAgB,OAAO,KAAK,OAAO;AAC7D,aAAS,OAAO,QAAQ,MAAM,IAAI;AAElC,QAAI,MAAM;AACT,eAAS,OAAO,mBAAmB,KAAK,cAAc;AACvD,QAAI,MAAM,SAAU,UAAS,OAAO,aAAa,KAAK,QAAQ;AAC9D,QAAI,MAAM,UAAW,UAAS,OAAO,cAAc,KAAK,SAAS;AAEjE,UAAM,OAAO,MAAM,KAAK,KAAK;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAO,QAAwC;AACpD,UAAM,OAAO,MAAM,KAAK,KAAK;AAAA,MAC5B;AAAA,MACA,YAAY,mBAAmB,MAAM;AAAA,IACtC;AACA,QAAI,QAAQ,OAAO,SAAS,YAAY,SAAS,MAAM;AACtD,aAAQ,KAAiC;AAAA,IAC1C;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAO,QAAgB,SAAyC;AACrE,WAAO,KAAK,KAAK;AAAA,MAChB;AAAA,MACA,YAAY,mBAAmB,MAAM;AAAA,MACrC;AAAA,MACA;AAAA,IACD;AAAA,EACD;AACD;;;ACFO,IAAM,iBAAN,MAAqB;AAAA;AAAA;AAAA;AAAA;AAAA,EAY3B,YAAY,SAAgC;AAP5C,SAAQ,kBAAkB,oBAAI,IAA+B;AAQ5D,UAAM,UAAU,QAAQ,QAAQ,QAAQ,QAAQ,EAAE;AAClD,SAAK,YACJ,QAAQ,aAAa,IAAI,UAAU,QAAQ,WAAW,aAAa;AACpE,SAAK,OAAO,IAAI,WAAW,SAAS,KAAK,SAAS;AAClD,SAAK,WAAW,QAAQ,YAAY,IAAI,gBAAgB;AAExD,QAAI,CAAC,QAAQ,UAAU;AACtB,WAAK,SAAS,OAAO,iBAAiB,OAAO,CAAC;AAAA,IAC/C;AACA,SAAK,QAAQ,IAAI,aAAa,KAAK,IAAI;AACvC,QAAI,QAAQ,OAAO,SAAS;AAC3B,WAAK,eAAe,IAAI;AAAA,QACvB,QAAQ,MAAM,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;AAAA,MAC7C;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,WAAW,MAA0C;AACpD,QAAI,MAAM,KAAK,gBAAgB,IAAI,IAAI;AACvC,QAAI,CAAC,KAAK;AACT,YAAM,IAAI;AAAA,QACT,KAAK;AAAA,QACL;AAAA,QACA,KAAK;AAAA,QACL,KAAK;AAAA,MACN;AACA,WAAK,gBAAgB,IAAI,MAAM,GAAG;AAAA,IACnC;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAAmC,MAA0C;AAC5E,WAAO,KAAK,WAAW,IAAI;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,SAA4C;AACzD,UAAM,UAAU,KAAK,eAAe,CAAC,GAAG,KAAK,aAAa,OAAO,CAAC,IAAI,CAAC;AACvE,WAAO,cAAc,SAAS,OAAO;AAAA,EACtC;AAAA;AAAA;AAAA,EAKA,MAAM,iBAA6D;AAClE,WAAO,KAAK,KAAK,IAAgC,mBAAmB;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,MACL,OACA,UACgE;AAChE,UAAM,OAAO,MAAM,KAAK,KAAK,KAE3B,qBAAqB,EAAE,OAAO,SAAS,CAAC;AAC1C,QAAI,MAAM;AACT,WAAK,UAAU,kBAAkB,IAAI;AACrC,WAAK,UAAU,IAAI,KAAK,OAAO,IAAI;AAAA,IACpC;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,MACL,OACA,UACA,YACgE;AAChE,QAAI;AACJ,QAAI,YAAY;AACf,aAAO,MAAM,KAAK,KAAK,KAKrB,MAAM,mBAAmB,UAAU,IAAI,uBAAuB;AAAA,QAC/D,UAAU;AAAA,QACV;AAAA,MACD,CAAC;AACD,UAAI,QAAQ,KAAK,QAAQ;AACxB,aAAK,UAAU,kBAAkB,UAAU;AAC3C,aAAK,UAAU,IAAI,KAAK,OAAO,KAAK,MAA8B;AAAA,MACnE;AAAA,IACD,OAAO;AACN,aAAO,MAAM,KAAK,KAAK;AAAA,QACtB;AAAA,QACA,EAAE,OAAO,SAAS;AAAA,MACnB;AACA,UAAI,MAAM;AACT,aAAK,UAAU,kBAAkB,IAAI;AACrC,aAAK,UAAU,IAAI,KAAK,OAAO,IAAI;AAAA,MACpC;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,MAAM,GAAkB,SAA6C;AACpE,UAAM,OAAO,MAAM,KAAK,KAAK,IAAO,kBAAkB,OAAO;AAC7D,QAAI,MAAM;AAET,WAAK,UAAU,IAAI,KAAK,UAAU,OAAO,IAA4B;AAAA,IACtE;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,iBACL,YACA,UACA,UACA,SAGC;AACD,UAAM,OAAO,MAAM,KAAK,KAAK;AAAA,MAG5B,MAAM,mBAAmB,UAAU,IAAI;AAAA,MACvC,EAAE,UAAU,SAAS;AAAA,MACrB;AAAA,IACD;AACA,QAAI,MAAM;AACT,WAAK,UAAU,kBAAkB,UAAU;AAC3C,WAAK,UAAU,IAAI,KAAK,OAAO,KAAK,MAA8B;AAAA,IACnE;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YACL,YACA,SAGC;AACD,UAAM,OAAO,MAAM,KAAK,KAAK;AAAA,MAG5B,MAAM,mBAAmB,UAAU,IAAI;AAAA,MACvC;AAAA,MACA;AAAA,IACD;AACA,QAAI,MAAM;AACT,WAAK,UAAU,kBAAkB,UAAU;AAC3C,WAAK,UAAU,IAAI,KAAK,OAAO,KAAK,MAA8B;AAAA,IACnE;AACA,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,SAAe;AACd,SAAK,UAAU,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA,EAKA,OAAO,SAAmE;AACzE,WAAO,KAAK,KAAK,IAA6B,WAAW,OAAO;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,gBACC,GACA,SACwC;AACxC,WAAO,KAAK,KAAK;AAAA,MAChB,kBAAkB,IAAI,MAAM,IAAI;AAAA,MAChC;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cACC,IACA,SAC4B;AAC5B,WAAO,KAAK,KAAK;AAAA,MAChB,kBAAkB,mBAAmB,EAAE;AAAA,MACvC;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBACC,MACA,SAC4B;AAC5B,WAAO,KAAK,KAAK,KAAgB,gBAAgB,MAAM,OAAO;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBACC,IACA,MACA,SAC4B;AAC5B,WAAO,KAAK,KAAK;AAAA,MAChB,kBAAkB,mBAAmB,EAAE;AAAA,MACvC;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,IAAY,SAAyC;AACrE,WAAO,KAAK,KAAK,OAAO,kBAAkB,mBAAmB,EAAE,GAAG,OAAO;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,YACC,MACA,QACA,SACwC;AACxC,UAAM,KAAK,SAAS,MAAM,IAAI,gBAAgB,MAAM,EAAE,SAAS,IAAI;AACnE,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,mBAAmB,IAAI,IAAI;AAAA,MACjC;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UACC,MACA,IACA,SAC4B;AAC5B,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,mBAAmB,IAAI,IAAI,MAAM,mBAAmB,EAAE;AAAA,MAC5D;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aACC,MACA,MACA,SAC4B;AAC5B,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,mBAAmB,IAAI;AAAA,MAC7B;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aACC,MACA,IACA,MACA,SAC4B;AAC5B,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,mBAAmB,IAAI,IAAI,MAAM,mBAAmB,EAAE;AAAA,MAC5D;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aACC,MACA,IACA,SACgB;AAChB,WAAO,KAAK,KAAK;AAAA,MAChB,MAAM,mBAAmB,IAAI,IAAI,MAAM,mBAAmB,EAAE;AAAA,MAC5D;AAAA,IACD;AAAA,EACD;AACD;;;ACldO,IAAM,cAAN,cAEG,eAAe;AAAA,EASf,WAAW,MAA0C;AAC7D,WAAO,MAAM,WAAW,IAAI;AAAA,EAC7B;AACD;AAMO,SAAS,aAEd,SAA2D;AAC5D,SAAO,IAAI,YAA0B,OAAO;AAC7C;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazypock",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -23,9 +23,16 @@
|
|
|
23
23
|
"build": "tsup",
|
|
24
24
|
"dev": "tsup --watch",
|
|
25
25
|
"typecheck": "tsc --noEmit",
|
|
26
|
+
"generate": "node dist/cli.js",
|
|
27
|
+
"smoke": "npm run build && node smoke-test.mjs && tsc --noEmit --strict --target es2020 --module esnext --moduleResolution bundler type-test.ts",
|
|
26
28
|
"prepublishOnly": "npm run build"
|
|
27
29
|
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"lazypock": "./dist/cli.js",
|
|
32
|
+
"lazypock-gen": "./dist/cli.js"
|
|
33
|
+
},
|
|
28
34
|
"devDependencies": {
|
|
35
|
+
"@types/node": "^26.1.2",
|
|
29
36
|
"tsup": "^8.4.0",
|
|
30
37
|
"typescript": "^5.9.3"
|
|
31
38
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ── Codegen CLI ─────────────────────────────────────────
|
|
3
|
+
// `lazypock` — fetches the live collection schema from a Lazypock
|
|
4
|
+
// API and writes a fully-typed `lazypock.types.ts` module.
|
|
5
|
+
//
|
|
6
|
+
// Auth methods (pick one):
|
|
7
|
+
// 1. Superuser email + password:
|
|
8
|
+
// npx lazypock --url http://localhost:4000/api --email admin@... --password ...
|
|
9
|
+
// 2. API key (recommended, generated from the Settings dashboard):
|
|
10
|
+
// npx lazypock --url http://localhost:4000/api --api-key <key>
|
|
11
|
+
//
|
|
12
|
+
// Or via env vars (no flags):
|
|
13
|
+
// LAZYPOCK_URL=... LAZYPOCK_API_KEY=... npx lazypock
|
|
14
|
+
// LAZYPOCK_URL=... LAZYPOCK_EMAIL=... LAZYPOCK_PASSWORD=... npx lazypock
|
|
15
|
+
//
|
|
16
|
+
// NOTE: `lazypock-gen` remains as a deprecated alias for backwards
|
|
17
|
+
// compatibility. Both invoke the same executable.
|
|
18
|
+
|
|
19
|
+
import { writeFile } from "node:fs/promises";
|
|
20
|
+
import { resolve } from "node:path";
|
|
21
|
+
import { generateTypes } from "./codegen";
|
|
22
|
+
import type { CollectionsResponse } from "./schema";
|
|
23
|
+
|
|
24
|
+
interface CliOptions {
|
|
25
|
+
url: string;
|
|
26
|
+
email: string;
|
|
27
|
+
password: string;
|
|
28
|
+
apiKey: string;
|
|
29
|
+
out: string;
|
|
30
|
+
packageName: string;
|
|
31
|
+
skipSystem: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fail(msg: string): never {
|
|
35
|
+
console.error(`\n❌ ${msg}\n`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseArgs(argv: string[]): CliOptions {
|
|
40
|
+
const args = [...argv];
|
|
41
|
+
const get = (flag: string, envKey: string, def = ""): string => {
|
|
42
|
+
const i = args.indexOf(flag);
|
|
43
|
+
if (i !== -1 && i + 1 < args.length) return args[i + 1];
|
|
44
|
+
return process.env[envKey] ?? def;
|
|
45
|
+
};
|
|
46
|
+
const has = (flag: string): boolean => args.includes(flag);
|
|
47
|
+
|
|
48
|
+
const url = get("--url", "LAZYPOCK_URL");
|
|
49
|
+
const email = get("--email", "LAZYPOCK_EMAIL");
|
|
50
|
+
const password = get("--password", "LAZYPOCK_PASSWORD");
|
|
51
|
+
const apiKey = get("--api-key", "LAZYPOCK_API_KEY");
|
|
52
|
+
|
|
53
|
+
if (!url) fail("Missing API URL. Pass --url or set LAZYPOCK_URL.");
|
|
54
|
+
if (!apiKey) {
|
|
55
|
+
if (!email)
|
|
56
|
+
fail(
|
|
57
|
+
"Missing credentials. Pass --api-key, or --email + --password, or set LAZYPOCK_API_KEY / LAZYPOCK_EMAIL.",
|
|
58
|
+
);
|
|
59
|
+
if (!password)
|
|
60
|
+
fail("Missing password. Pass --password, or set LAZYPOCK_PASSWORD.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const out = get("--out", "LAZYPOCK_OUT", "lazypock.types.ts");
|
|
64
|
+
const packageName = get("--package", "LAZYPOCK_PACKAGE", "lazypock");
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
url,
|
|
68
|
+
email,
|
|
69
|
+
password,
|
|
70
|
+
apiKey,
|
|
71
|
+
out,
|
|
72
|
+
packageName,
|
|
73
|
+
skipSystem: has("--skip-system"),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function fetchCollections(
|
|
78
|
+
opts: CliOptions,
|
|
79
|
+
): Promise<CollectionsResponse> {
|
|
80
|
+
const base = opts.url.replace(/\/+$/, "");
|
|
81
|
+
|
|
82
|
+
let authKey: string;
|
|
83
|
+
if (opts.apiKey) {
|
|
84
|
+
// Step 1: use a stored API key directly (no login round-trip).
|
|
85
|
+
// The key is sent as `Authorization: Bearer <key>` and recognised
|
|
86
|
+
// by the backend's Auth.Plug as an API key.
|
|
87
|
+
authKey = opts.apiKey;
|
|
88
|
+
} else {
|
|
89
|
+
// Step 1: login as superuser to get a token
|
|
90
|
+
const loginRes = await fetch(base + "/superusers/login", {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: { "Content-Type": "application/json" },
|
|
93
|
+
body: JSON.stringify({ email: opts.email, password: opts.password }),
|
|
94
|
+
});
|
|
95
|
+
if (!loginRes.ok) {
|
|
96
|
+
const text = await loginRes.text();
|
|
97
|
+
fail(
|
|
98
|
+
`Superuser login failed (${loginRes.status}). ${text.slice(0, 200)}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const loginData = (await loginRes.json()) as { token?: string };
|
|
102
|
+
if (!loginData.token) fail("Login response did not include a token.");
|
|
103
|
+
authKey = loginData.token;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Step 2: fetch collections
|
|
107
|
+
const collRes = await fetch(base + "/collections", {
|
|
108
|
+
headers: { Authorization: "Bearer " + authKey },
|
|
109
|
+
});
|
|
110
|
+
if (!collRes.ok) {
|
|
111
|
+
const text = await collRes.text();
|
|
112
|
+
fail(
|
|
113
|
+
`Failed to fetch collections (${collRes.status}). ${text.slice(0, 200)}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return (await collRes.json()) as CollectionsResponse;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function main(): Promise<void> {
|
|
120
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
121
|
+
const authLabel = opts.apiKey
|
|
122
|
+
? `API key ${opts.apiKey.slice(0, 4)}…${opts.apiKey.slice(-4)}`
|
|
123
|
+
: opts.email;
|
|
124
|
+
console.log(`\n🔌 Connecting to ${opts.url} as ${authLabel} …`);
|
|
125
|
+
|
|
126
|
+
const { items } = await fetchCollections(opts);
|
|
127
|
+
console.log(`📦 Found ${items.length} collection(s).`);
|
|
128
|
+
|
|
129
|
+
const source = generateTypes(items, {
|
|
130
|
+
packageName: opts.packageName,
|
|
131
|
+
skipSystem: opts.skipSystem,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const outPath = resolve(process.cwd(), opts.out);
|
|
135
|
+
await writeFile(outPath, source, "utf8");
|
|
136
|
+
console.log(`✅ Wrote ${outPath} (${source.length} bytes).`);
|
|
137
|
+
console.log(
|
|
138
|
+
`\nImport it in your app:\n import { createClient } from './${opts.out.replace(/\.ts$/, "")}';\n`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main().catch((err) => {
|
|
143
|
+
console.error(err);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// ── Typed client factory ────────────────────────────────
|
|
2
|
+
// Generic over a collections map, so collection access is type-checked
|
|
3
|
+
// without hand-writing interfaces — pair with the codegen CLI output.
|
|
4
|
+
|
|
5
|
+
import { LazypockClient } from "./lazypock";
|
|
6
|
+
import type { CollectionService } from "./collection";
|
|
7
|
+
import type { LazypockClientOptions } from "./lazypock";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A collections map: `{ posts: PostRecord; users: UserRecord; ... }`.
|
|
11
|
+
* Generated by the codegen CLI (`npx lazypock-gen`) or written by hand.
|
|
12
|
+
*/
|
|
13
|
+
export type LazypockCollections = Record<string, unknown>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Client typed against a {@link LazypockCollections} map.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { createClient } from "lazypock/generated";
|
|
20
|
+
* const client = createClient({ baseUrl: "http://localhost:4000/api" });
|
|
21
|
+
* const posts = await client.collection("posts").list<PostsRecord>();
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class TypedClient<
|
|
25
|
+
TCollections extends LazypockCollections = LazypockCollections,
|
|
26
|
+
> extends LazypockClient {
|
|
27
|
+
/**
|
|
28
|
+
* Get a typed service for a collection.
|
|
29
|
+
* When `TCollections` is provided, unknown collection names are rejected.
|
|
30
|
+
* @typeParam K — Collection name (keyof TCollections).
|
|
31
|
+
*/
|
|
32
|
+
override collection<K extends keyof TCollections>(
|
|
33
|
+
name: K,
|
|
34
|
+
): CollectionService<TCollections[K]>;
|
|
35
|
+
override collection(name: string): CollectionService<unknown> {
|
|
36
|
+
return super.collection(name) as unknown as CollectionService<unknown>;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a {@link TypedClient}.
|
|
42
|
+
* @typeParam TCollections — Map of collection name → record shape.
|
|
43
|
+
*/
|
|
44
|
+
export function createClient<
|
|
45
|
+
TCollections extends LazypockCollections = LazypockCollections,
|
|
46
|
+
>(options: LazypockClientOptions): TypedClient<TCollections> {
|
|
47
|
+
return new TypedClient<TCollections>(options);
|
|
48
|
+
}
|