@happyvertical/smrt-web 0.37.10 → 0.37.11
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/AGENTS.md +5 -1
- package/dist/index.d.ts +38 -4
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -14,7 +14,11 @@ mutations without hand-wiring cache keys or fetch/state.
|
|
|
14
14
|
generated `@happyvertical/smrt-virt-web` definition. Stale-while-revalidate
|
|
15
15
|
reads (`staleTimeMs`, default 30s); N concurrent identical reads coalesce into
|
|
16
16
|
one request; optimistic inserts persist through the REST surface and roll back
|
|
17
|
-
automatically on server error.
|
|
17
|
+
automatically on server error. Pass `initialData` (SMRT-owned
|
|
18
|
+
`SmrtWebRow<T>[]`) to seed the cache from server-rendered rows so the first
|
|
19
|
+
client read serves them WITHOUT a duplicate first-render fetch — the SvelteKit
|
|
20
|
+
`+page.server.ts` → hydrate path (#1761). The seed is fresh for `staleTimeMs`;
|
|
21
|
+
fold the same `scope` used for reads into it under a shared `client`.
|
|
18
22
|
- `createSmrtWebClient()` — an opaque shared-cache handle. Pass one app-wide
|
|
19
23
|
instance so collections share a cache and deduplicate requests.
|
|
20
24
|
- `createDefinitionFetchers(definition, basePath, fetchFn)` — CRUD fetchers
|
package/dist/index.d.ts
CHANGED
|
@@ -25,10 +25,21 @@ export declare function createDefinitionFetchers(definition: SmrtWebCollectionDe
|
|
|
25
25
|
* dependent views refetch. Reaching OTHER collections requires them to share
|
|
26
26
|
* this collection's `client` (see {@link createSmrtWebClient}); with a private
|
|
27
27
|
* client only this collection refetches.
|
|
28
|
+
*
|
|
29
|
+
* Hydration seeding: pass rows fetched server-side as
|
|
30
|
+
* {@link CreateSmrtCollectionOptions.initialData} and the collection's first
|
|
31
|
+
* read is served from them with NO network request (until `staleTimeMs`
|
|
32
|
+
* elapses) — the SvelteKit `+page.server.ts` → hydrate path.
|
|
28
33
|
*/
|
|
29
|
-
export declare function createSmrtCollection<TData extends object>(definition: SmrtWebCollectionDefinition<TData>, options: CreateSmrtCollectionOptions): SmrtWebCollection<TData>;
|
|
34
|
+
export declare function createSmrtCollection<TData extends object>(definition: SmrtWebCollectionDefinition<TData>, options: CreateSmrtCollectionOptions<TData>): SmrtWebCollection<TData>;
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Options for {@link createSmrtCollection}. Generic in the collection's row
|
|
38
|
+
* type `TData` so {@link initialData} is checked against the same DTO the
|
|
39
|
+
* collection stores; every other option is row-type-agnostic, so the parameter
|
|
40
|
+
* defaults to `object` and can be omitted at call sites that pass no seed.
|
|
41
|
+
*/
|
|
42
|
+
export declare interface CreateSmrtCollectionOptions<TData extends object = object> {
|
|
32
43
|
/**
|
|
33
44
|
* Generated REST client surface for this collection, e.g.
|
|
34
45
|
* `createClient('/api/v1').products` from the virt-client module. When
|
|
@@ -63,6 +74,25 @@ export declare interface CreateSmrtCollectionOptions {
|
|
|
63
74
|
staleTimeMs?: number;
|
|
64
75
|
/** Retry failed loads (default false: fail fast, surface errors). */
|
|
65
76
|
retry?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Rows to seed this collection's cache with, before its first read — the
|
|
79
|
+
* hydration path for server-rendered data (#1761). Fetch rows in a SvelteKit
|
|
80
|
+
* `+page.server.ts` load, pass them here on the client, and the first read
|
|
81
|
+
* serves them from cache WITHOUT a duplicate first-render network request
|
|
82
|
+
* (SMRT-owned type, so no engine type appears on the option).
|
|
83
|
+
*
|
|
84
|
+
* The seed is written to the cache with a fresh timestamp, so it counts as
|
|
85
|
+
* fresh for `staleTimeMs`: with the default window the first read does not
|
|
86
|
+
* fetch, and the collection revalidates in the background only once the window
|
|
87
|
+
* elapses (or immediately if `staleTimeMs` is 0). Seed the SAME rows the
|
|
88
|
+
* server serialized so the pre- and post-hydration renders match.
|
|
89
|
+
*
|
|
90
|
+
* Seeds the SAME cache key the reads use — so with a shared {@link client},
|
|
91
|
+
* fold the backend / tenant discriminator into {@link scope} to match, exactly
|
|
92
|
+
* as reads do; otherwise one backend's seed would serve the other for the
|
|
93
|
+
* `staleTimeMs` window.
|
|
94
|
+
*/
|
|
95
|
+
initialData?: SmrtWebRow<TData>[];
|
|
66
96
|
}
|
|
67
97
|
|
|
68
98
|
/**
|
|
@@ -207,9 +237,13 @@ export declare interface SmrtWebCollectionDefinition<TData extends object = obje
|
|
|
207
237
|
* cache keys. Cross-collection reach requires a shared client from
|
|
208
238
|
* {@link createSmrtWebClient}; with a private client only the mutated
|
|
209
239
|
* collection refetches.
|
|
240
|
+
* - hydration seeding (#1761): rows fetched server-side (a SvelteKit
|
|
241
|
+
* `+page.server.ts` load) seed the shared cache via
|
|
242
|
+
* {@link CreateSmrtCollectionOptions.initialData}, so the first client read
|
|
243
|
+
* serves them WITHOUT a duplicate first-render fetch.
|
|
210
244
|
*
|
|
211
|
-
* Deliberately NOT here yet (see PRD #1755):
|
|
212
|
-
*
|
|
245
|
+
* Deliberately NOT here yet (see PRD #1755): offline outbox, SSE invalidation,
|
|
246
|
+
* persistence, version awareness.
|
|
213
247
|
*/
|
|
214
248
|
/**
|
|
215
249
|
* Field metadata emitted per column by the `@happyvertical/smrt-virt-web`
|
package/dist/index.js
CHANGED
|
@@ -102,7 +102,7 @@ function getEngineCollection(handle) {
|
|
|
102
102
|
return engine;
|
|
103
103
|
}
|
|
104
104
|
function createSmrtCollection(definition, options) {
|
|
105
|
-
const { staleTimeMs = 3e4, retry = false, scope } = options;
|
|
105
|
+
const { staleTimeMs = 3e4, retry = false, scope, initialData } = options;
|
|
106
106
|
const fetchers = options.fetchers ?? createDefinitionFetchers(definition, options.basePath, options.fetchFn);
|
|
107
107
|
const queryClient = resolveQueryClient(options.client);
|
|
108
108
|
const idField = definition.idField || "id";
|
|
@@ -112,6 +112,7 @@ function createSmrtCollection(definition, options) {
|
|
|
112
112
|
scope,
|
|
113
113
|
definition.name
|
|
114
114
|
] : ["smrt", definition.name];
|
|
115
|
+
if (initialData !== void 0) queryClient.setQueryData(queryKey, (existing) => existing ?? initialData);
|
|
115
116
|
const invalidationTargets = /* @__PURE__ */ new Set([definition.name]);
|
|
116
117
|
for (const relationship of definition.relationships ?? []) invalidationTargets.add(relationship.relatedCollection);
|
|
117
118
|
const invalidateRelated = () => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @happyvertical/smrt-web — browser client data runtime (#1761).\n *\n * A typed collection factory that materializes the manifest-generated web\n * collection definitions (`@happyvertical/smrt-virt-web`) as cached, reactive\n * collections over the generated SMRT REST surface.\n *\n * This package is the **engine-absorption boundary**: the client-data engine\n * (currently TanStack DB) is an implementation detail held entirely inside\n * this module. Its types never appear on the public API — collections are\n * handed back as the SMRT-owned {@link SmrtWebCollection}, and the shared cache\n * as the opaque {@link SmrtWebClient} — so the engine stays swappable without a\n * consumer-visible break. Consumers never import `@tanstack/*` directly.\n *\n * Framework-agnostic by construction: this entry imports no UI framework.\n * Svelte live-query bindings ship separately (see PRD #1755) so this core never\n * pulls the Svelte-only `@tanstack/svelte-db` export condition.\n *\n * Scope of this slice:\n * - stale-while-revalidate reads (a `staleTimeMs` window, background revalidation)\n * - concurrent-read dedup (one network request per in-flight collection load)\n * - optimistic create that persists through the generated REST surface and\n * rolls back automatically when the server errors\n * - relationship-derived invalidation (#1761): a settled mutation invalidates\n * the caches of the collections related to the mutated one, with the edges\n * derived from the manifest (`definition.relationships`) — no hand-wired\n * cache keys. Cross-collection reach requires a shared client from\n * {@link createSmrtWebClient}; with a private client only the mutated\n * collection refetches.\n *\n * Deliberately NOT here yet (see PRD #1755): SvelteKit hydration seeding,\n * offline outbox, SSE invalidation, persistence, version awareness.\n */\n\nimport { createCollection } from '@tanstack/db';\nimport { QueryClient } from '@tanstack/query-core';\nimport { queryCollectionOptions } from '@tanstack/query-db-collection';\n\n// ---------------------------------------------------------------------------\n// Generated definition contract (mirrors @happyvertical/smrt-virt-web)\n// ---------------------------------------------------------------------------\n\n/**\n * Field metadata emitted per column by the `@happyvertical/smrt-virt-web`\n * virtual module (generated from the package manifest).\n */\nexport interface SmrtWebFieldDefinition {\n type: string;\n required?: boolean;\n default?: unknown;\n}\n\n/** The relationship kinds a generated web collection edge can describe. */\nexport type SmrtWebRelationshipKind =\n | 'foreignKey'\n | 'crossPackageRef'\n | 'oneToMany'\n | 'manyToMany';\n\n/**\n * A manifest-derived edge from this collection to a sibling REST collection,\n * emitted by the `@happyvertical/smrt-virt-web` virtual module. When a mutation\n * on this collection settles, the caches of the collections named by these\n * edges are invalidated (relationship-derived invalidation, #1761), so a\n * dependent view refetches without any hand-wired cache key.\n *\n * SMRT-owned data — no client-engine (`@tanstack/*`) type appears here, so it\n * stays inside the engine-absorption boundary.\n */\nexport interface SmrtWebRelationship {\n /** The declaring field carrying the relationship (e.g. `groupId`, `items`). */\n field: string;\n /** The relationship kind, mirroring the manifest field type. */\n kind: SmrtWebRelationshipKind;\n /** REST collection name the edge resolves to (e.g. `ad_groups`). */\n relatedCollection: string;\n}\n\n/**\n * One generated collection definition: everything needed to construct a client\n * collection over the generated REST surface. The `_row` property is a phantom\n * type carrier threaded through codegen — it never exists at runtime, it only\n * lets factories infer the row type from a definition.\n */\nexport interface SmrtWebCollectionDefinition<TData extends object = object> {\n /** REST collection name (e.g. `products`). */\n name: string;\n /** Source class name (e.g. `Product`). */\n className: string;\n /** Path under the API base path (e.g. `/products`). */\n endpoint: string;\n /** Primary key field name (`id` for SmrtObject). */\n idField: string;\n /** CRUD + custom actions exposed by the api decorator config. */\n actions: string[];\n /** Persisted field metadata keyed by field name. */\n fields: Record<string, SmrtWebFieldDefinition>;\n /**\n * Manifest-derived relationship edges to sibling REST collections. Drives\n * relationship-derived cache invalidation: a settled mutation on this\n * collection invalidates the caches of the collections these edges name.\n * Optional so hand-built definitions (older codegen, tests) still satisfy the\n * type; a missing value means \"no derived edges\".\n */\n relationships?: SmrtWebRelationship[];\n /** Phantom row-type carrier — never present at runtime. */\n _row?: TData;\n}\n\n// ---------------------------------------------------------------------------\n// Fetcher contract + payload normalization\n// ---------------------------------------------------------------------------\n\n/**\n * The per-collection CRUD surface of the generated REST client\n * (`createClient(basePath).<collection>` from `@happyvertical/smrt-virt-client`).\n *\n * Return types are `unknown` on purpose: generated fetchers resolve with\n * whatever the server sent, so this package normalizes and validates payloads\n * centrally — see {@link unwrapListResult} / {@link unwrapItemResult}.\n */\nexport interface SmrtCrudFetchers {\n list(params?: Record<string, unknown>): Promise<unknown>;\n get?(id: string): Promise<unknown>;\n create(data: Record<string, unknown>): Promise<unknown>;\n update?(id: string, data: Record<string, unknown>): Promise<unknown>;\n delete?(id: string): Promise<unknown>;\n}\n\n/**\n * Raised when a generated-client call resolved with an error payload\n * (`{ error: string }` from the generated REST routes) or an unexpected shape.\n * Thrown inside a mutation handler, this triggers the automatic rollback of\n * optimistic state.\n */\nexport class SmrtWebRequestError extends Error {\n readonly payload: unknown;\n\n constructor(message: string, payload?: unknown) {\n super(message);\n this.name = 'SmrtWebRequestError';\n this.payload = payload;\n }\n}\n\n/** A row as stored in the client collection: the DTO plus a required key. */\nexport type SmrtWebRow<TData extends object> = TData & { id: string };\n\n/**\n * Normalize a generated-client list result to an array of rows.\n *\n * The generated REST routes return a bare JSON array; `{ error }` payloads are\n * surfaced as failures. The `{ data: [...] }` envelope is tolerated for\n * ApiResponse-shaped clients (e.g. a mock client).\n */\nexport function unwrapListResult(\n result: unknown,\n collectionName: string,\n): Array<Record<string, unknown>> {\n if (Array.isArray(result)) {\n return result as Array<Record<string, unknown>>;\n }\n if (result && typeof result === 'object') {\n const record = result as Record<string, unknown>;\n if (typeof record.error === 'string') {\n throw new SmrtWebRequestError(\n `[smrt-web] list(${collectionName}) failed: ${record.error}`,\n result,\n );\n }\n if (Array.isArray(record.data)) {\n return record.data as Array<Record<string, unknown>>;\n }\n }\n throw new SmrtWebRequestError(\n `[smrt-web] list(${collectionName}) returned an unexpected payload shape`,\n result,\n );\n}\n\n/**\n * Normalize a generated-client item result (create/update) to a row.\n * `{ error }` payloads become failures — inside mutation handlers this is what\n * makes optimistic state roll back.\n */\nexport function unwrapItemResult(\n result: unknown,\n context: string,\n): Record<string, unknown> {\n if (result && typeof result === 'object' && !Array.isArray(result)) {\n const record = result as Record<string, unknown>;\n if (typeof record.error === 'string') {\n throw new SmrtWebRequestError(\n `[smrt-web] ${context} failed: ${record.error}`,\n result,\n );\n }\n if (\n record.data &&\n typeof record.data === 'object' &&\n !Array.isArray(record.data)\n ) {\n return record.data as Record<string, unknown>;\n }\n return record;\n }\n throw new SmrtWebRequestError(\n `[smrt-web] ${context} returned an unexpected payload shape`,\n result,\n );\n}\n\n/**\n * Build CRUD fetchers from a generated collection definition — the same URL\n * scheme and payload handling as the generated REST client\n * (`basePath + endpoint`), with one improvement: HTTP error statuses reject\n * with the server's `{ error }` body instead of resolving with it.\n */\nexport function createDefinitionFetchers(\n definition: SmrtWebCollectionDefinition<object>,\n basePath = '/api/v1',\n fetchFn: typeof fetch = (...args) => globalThis.fetch(...args),\n): SmrtCrudFetchers {\n const collectionUrl = `${basePath}${definition.endpoint}`;\n const headers = { 'Content-Type': 'application/json' };\n\n const parse = async (response: Response): Promise<unknown> => {\n const payload: unknown = await response.json().catch(() => null);\n if (!response.ok) {\n const message =\n payload &&\n typeof payload === 'object' &&\n typeof (payload as Record<string, unknown>).error === 'string'\n ? String((payload as Record<string, unknown>).error)\n : `HTTP ${response.status}`;\n throw new SmrtWebRequestError(\n `[smrt-web] ${definition.name} request failed: ${message}`,\n payload,\n );\n }\n return payload;\n };\n\n return {\n list: async () => parse(await fetchFn(collectionUrl, { headers })),\n get: async (id) =>\n parse(await fetchFn(`${collectionUrl}/${id}`, { headers })),\n create: async (data) =>\n parse(\n await fetchFn(collectionUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(data),\n }),\n ),\n update: async (id, data) =>\n parse(\n await fetchFn(`${collectionUrl}/${id}`, {\n method: 'PUT',\n headers,\n body: JSON.stringify(data),\n }),\n ),\n delete: async (id) => {\n const response = await fetchFn(`${collectionUrl}/${id}`, {\n method: 'DELETE',\n headers,\n });\n if (!response.ok) {\n throw new SmrtWebRequestError(\n `[smrt-web] delete(${definition.name}) failed: HTTP ${response.status}`,\n );\n }\n return true;\n },\n };\n}\n\n/**\n * Generate a client-local id for optimistic inserts. The generated REST layer\n * strips client-supplied ids on create (mass-assignment guard #1540), so this\n * id only identifies the optimistic row until the post-persist refetch swaps in\n * the server-assigned row.\n */\nexport function newLocalId(): string {\n const cryptoRef = globalThis.crypto as Crypto | undefined;\n if (cryptoRef?.randomUUID) {\n return cryptoRef.randomUUID();\n }\n return `local-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Engine-absorbing public surface (no @tanstack/* types leak past here)\n// ---------------------------------------------------------------------------\n\n/**\n * Opaque handle to the shared client cache / request-dedup layer. Create one\n * with {@link createSmrtWebClient} and pass the SAME instance to every\n * collection that should share a cache and deduplicate in-flight requests.\n *\n * The engine (currently a TanStack Query client) is intentionally hidden behind\n * this brand so it stays swappable — do not depend on its concrete shape.\n */\nexport interface SmrtWebClient {\n /** Phantom brand — this handle wraps the hidden client-cache engine. */\n readonly __smrtWebClient: 'SmrtWebClient';\n}\n\n/**\n * Engine-side shape of a {@link SmrtWebClient}. Never exported, so the engine\n * type never reaches the public surface. Extends the public brand so the value\n * created here carries the brand at runtime (enabling the validation below).\n */\ninterface SmrtWebClientEngine extends SmrtWebClient {\n readonly queryClient: QueryClient;\n}\n\n/**\n * Create a shared client-cache handle. Pass the returned handle as\n * {@link CreateSmrtCollectionOptions.client} to every collection that should\n * share a cache and deduplicate requests app-wide.\n */\nexport function createSmrtWebClient(): SmrtWebClient {\n const engine: SmrtWebClientEngine = {\n __smrtWebClient: 'SmrtWebClient',\n queryClient: new QueryClient(),\n };\n return engine;\n}\n\nfunction resolveQueryClient(client?: SmrtWebClient): QueryClient {\n if (!client) return new QueryClient();\n const engine = client as Partial<SmrtWebClientEngine>;\n if (engine.__smrtWebClient !== 'SmrtWebClient' || !engine.queryClient) {\n throw new SmrtWebRequestError(\n '[smrt-web] options.client must be a handle from createSmrtWebClient()',\n );\n }\n return engine.queryClient;\n}\n\n/**\n * Project an engine row to a plain public DTO. The client-data engine decorates\n * stored rows with enumerable virtual props (`$synced`/`$origin`/`$key`/\n * `$collectionId`) that would otherwise cross the SMRT boundary through spread\n * or JSON serialization. The `$` prefix is reserved for the engine; SMRT\n * columns never begin with it.\n */\nfunction toPlainRow<TData extends object>(row: unknown): SmrtWebRow<TData> {\n const plain: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row as Record<string, unknown>)) {\n if (key.charCodeAt(0) !== 36 /* '$' */) plain[key] = value;\n }\n return plain as SmrtWebRow<TData>;\n}\n\n/** Project the row values carried by a change notification to plain DTOs. */\nfunction projectChanges(changes: unknown): unknown {\n if (!Array.isArray(changes)) return changes;\n return changes.map((change) => {\n if (!change || typeof change !== 'object') return change;\n const record = change as Record<string, unknown>;\n const projected: Record<string, unknown> = { ...record };\n if (record.value && typeof record.value === 'object') {\n projected.value = toPlainRow(record.value);\n }\n if (record.previousValue && typeof record.previousValue === 'object') {\n projected.previousValue = toPlainRow(record.previousValue);\n }\n return projected;\n });\n}\n\n/**\n * A pending optimistic mutation. Await {@link isPersisted} to observe the\n * server outcome: it resolves once the write has been persisted through the\n * REST surface, and rejects (rolling the optimistic state back) on error.\n */\nexport interface SmrtWebTransaction {\n readonly isPersisted: { readonly promise: Promise<unknown> };\n}\n\n/** A change-subscription handle. Call {@link unsubscribe} to detach. */\nexport interface SmrtWebSubscription {\n unsubscribe(): void;\n}\n\n/**\n * A live, cached collection of plain-DTO rows — the SMRT-owned public contract\n * over the client-data engine. Exposes only the committed surface; the engine's\n * own type is never named here so it stays swappable.\n */\nexport interface SmrtWebCollection<TData extends object> {\n /** All rows currently in the collection (plain DTOs, insertion order). */\n readonly toArray: ReadonlyArray<SmrtWebRow<TData>>;\n /** Number of rows currently in the collection. */\n readonly size: number;\n /** True when a row with `key` is present. */\n has(key: string): boolean;\n /** The row with `key`, or `undefined`. */\n get(key: string): SmrtWebRow<TData> | undefined;\n /** Resolve once the first load has completed. */\n preload(): Promise<void>;\n /** Tear down subscriptions and cached state. */\n cleanup(): Promise<void>;\n /** Subscribe to change notifications; returns a detach handle. */\n subscribeChanges(callback: (changes: unknown) => void): SmrtWebSubscription;\n /**\n * Optimistically insert a row and persist it through the create fetcher. The\n * row is visible synchronously; the returned transaction settles on the\n * server outcome (see {@link SmrtWebTransaction}).\n */\n insert(row: SmrtWebRow<TData>): SmrtWebTransaction;\n}\n\nexport interface CreateSmrtCollectionOptions {\n /**\n * Generated REST client surface for this collection, e.g.\n * `createClient('/api/v1').products` from the virt-client module. When\n * omitted, fetchers are derived from the definition's endpoint and `basePath`\n * with the same URL scheme and payload shapes the generated client uses.\n */\n fetchers?: SmrtCrudFetchers;\n /** API base path for definition-derived fetchers (default `/api/v1`). */\n basePath?: string;\n /** Fetch implementation override (tests, SSR). Defaults to global fetch. */\n fetchFn?: typeof fetch;\n /**\n * Shared cache handle from {@link createSmrtWebClient}. Pass one app-wide\n * instance so collections share a cache and deduplicate requests; a private\n * cache is created when omitted.\n */\n client?: SmrtWebClient;\n /**\n * Cache namespace for this collection's reads. Fold a backend / tenant /\n * preview discriminator in here when the SAME generated collection is\n * materialized against DIFFERENT backends while sharing one {@link client} —\n * without it those reads share a cache key and could serve one backend's rows\n * for the other for the whole `staleTimeMs` window. Omit for the common\n * single-backend case.\n */\n scope?: string;\n /**\n * Stale-while-revalidate window in milliseconds (default 30s): reads within\n * the window are served from the local collection without a network request;\n * the first read after it revalidates in the background.\n */\n staleTimeMs?: number;\n /** Retry failed loads (default false: fail fast, surface errors). */\n retry?: boolean;\n}\n\n/**\n * Registry mapping a public collection handle to its underlying engine\n * collection. Keyed weakly so a handle and its engine collection are collected\n * together. Read only through {@link getEngineCollection}.\n */\nconst engineCollections = new WeakMap<object, unknown>();\n\n/**\n * Retrieve the underlying engine collection backing a handle — an advanced\n * bridge for trusted framework bindings (e.g. the smrt-svelte live-query\n * binding), which must feed the engine collection to the query builder. Returns\n * `unknown` so no engine type crosses the boundary; callers cast. Throws for a\n * handle not produced by {@link createSmrtCollection}. Not needed for normal\n * use.\n */\nexport function getEngineCollection<TData extends object>(\n handle: SmrtWebCollection<TData>,\n): unknown {\n const engine = engineCollections.get(handle);\n if (engine === undefined) {\n throw new SmrtWebRequestError(\n '[smrt-web] getEngineCollection: not a smrt-web collection handle',\n );\n }\n return engine;\n}\n\n/**\n * Create a typed client collection over a generated SMRT collection definition\n * and the matching generated REST client fetchers.\n *\n * Reads: stale-while-revalidate. The first subscriber triggers a fetch;\n * re-subscribing within `staleTimeMs` serves local data with no request. N\n * concurrent identical reads coalesce into one network request.\n *\n * Writes: `collection.insert({ ...data, id: newLocalId() })` applies instantly,\n * persists through `fetchers.create()` (the temp id is stripped — the server\n * assigns the real one), then refetches to reconcile. A failed create rejects\n * the transaction and the optimistic row rolls back automatically.\n *\n * Relationship-derived invalidation: once a create/update/delete has persisted,\n * the query caches of this collection AND the collections named by\n * `definition.relationships` (manifest-derived edges) are invalidated, so\n * dependent views refetch. Reaching OTHER collections requires them to share\n * this collection's `client` (see {@link createSmrtWebClient}); with a private\n * client only this collection refetches.\n */\nexport function createSmrtCollection<TData extends object>(\n definition: SmrtWebCollectionDefinition<TData>,\n options: CreateSmrtCollectionOptions,\n): SmrtWebCollection<TData> {\n type Row = SmrtWebRow<TData>;\n\n const { staleTimeMs = 30_000, retry = false, scope } = options;\n const fetchers =\n options.fetchers ??\n createDefinitionFetchers(definition, options.basePath, options.fetchFn);\n const queryClient = resolveQueryClient(options.client);\n const idField = definition.idField || 'id';\n\n // Scope discriminates the cache key so a shared client can materialize the\n // same collection against different backends without cross-serving reads.\n const cacheId = scope\n ? `smrt:${scope}:${definition.name}`\n : `smrt:${definition.name}`;\n const queryKey = scope\n ? ['smrt', scope, definition.name]\n : ['smrt', definition.name];\n\n // Relationship-derived invalidation target set (#1761): the collections\n // whose caches a settled mutation on THIS collection must invalidate. Always\n // includes this collection itself (so its own read revalidates) plus every\n // manifest-derived related collection. Built once; a settled write matches\n // any cached query whose collection-name segment (the LAST queryKey element,\n // mirroring the `['smrt', (scope,) name]` scheme above) is in this set.\n //\n // Over-invalidation is safe — a stale query merely refetches. Under-\n // invalidation is the bug (a dependent view showing stale rows), so the\n // predicate matches by collection name across ALL scopes rather than an exact\n // key: a mutation in one scope refreshes the related collection in every\n // scope sharing the client.\n const invalidationTargets = new Set<string>([definition.name]);\n for (const relationship of definition.relationships ?? []) {\n invalidationTargets.add(relationship.relatedCollection);\n }\n\n /**\n * Invalidate the query caches of this collection and its manifest-derived\n * related collections. Cross-collection reach requires those collections to\n * share this collection's `client` (from {@link createSmrtWebClient}); with a\n * private client only THIS collection's query lives here, so only it\n * refetches. Fire-and-forget: invalidation schedules a background refetch and\n * must not delay the mutation's own settle.\n */\n const invalidateRelated = (): void => {\n void queryClient.invalidateQueries({\n predicate: (query) => {\n const key = query.queryKey;\n if (!Array.isArray(key) || key.length === 0) return false;\n const collectionSegment = key[key.length - 1];\n return (\n typeof collectionSegment === 'string' &&\n invalidationTargets.has(collectionSegment)\n );\n },\n });\n };\n\n const collection = createCollection(\n queryCollectionOptions<Row>({\n id: cacheId,\n queryKey,\n queryClient,\n staleTime: staleTimeMs,\n retry,\n queryFn: async () =>\n unwrapListResult(await fetchers.list(), definition.name) as Array<Row>,\n getKey: (row) => String((row as Record<string, unknown>)[idField]),\n onInsert: async ({ transaction }) => {\n for (const mutation of transaction.mutations) {\n const modified = mutation.modified as Record<string, unknown>;\n // Strip the client-local id: the generated REST layer rejects or\n // ignores client-supplied ids on create (#1540); the follow-up\n // refetch swaps the optimistic row for the server-assigned one.\n const { [idField]: _localId, ...data } = modified;\n unwrapItemResult(\n await fetchers.create(data),\n `create(${definition.name})`,\n );\n }\n // Persisted: refresh this collection and its related collections. Runs\n // only after every create resolved — a rejected create rolls the\n // optimistic row back and never reaches here.\n invalidateRelated();\n },\n onUpdate: fetchers.update\n ? async ({ transaction }) => {\n for (const mutation of transaction.mutations) {\n const key = String(mutation.key);\n const changes = mutation.changes as Record<string, unknown>;\n unwrapItemResult(\n // biome-ignore lint/style/noNonNullAssertion: guarded by the surrounding ternary\n await fetchers.update!(key, changes),\n `update(${definition.name})`,\n );\n }\n invalidateRelated();\n }\n : undefined,\n onDelete: fetchers.delete\n ? async ({ transaction }) => {\n for (const mutation of transaction.mutations) {\n // biome-ignore lint/style/noNonNullAssertion: guarded by the surrounding ternary\n await fetchers.delete!(String(mutation.key));\n }\n invalidateRelated();\n }\n : undefined,\n }),\n );\n\n // Wrap the engine collection in the SMRT-owned public surface. The wrapper\n // projects rows to plain DTOs at every read boundary (toArray/get and change\n // payloads) so the engine's virtual props never escape, and confines the\n // engine's own types to this module.\n const handle: SmrtWebCollection<TData> = {\n get toArray() {\n return collection.toArray.map((row) => toPlainRow<TData>(row));\n },\n get size() {\n return collection.size;\n },\n has(key) {\n return collection.has(key);\n },\n get(key) {\n const row = collection.get(key);\n return row === undefined ? undefined : toPlainRow<TData>(row);\n },\n preload() {\n return collection.preload();\n },\n cleanup() {\n return collection.cleanup();\n },\n subscribeChanges(callback) {\n const subscription = collection.subscribeChanges((changes: unknown) =>\n callback(projectChanges(changes)),\n );\n return { unsubscribe: () => subscription.unsubscribe() };\n },\n insert(row) {\n return collection.insert(row) as unknown as SmrtWebTransaction;\n },\n };\n\n engineCollections.set(handle, collection);\n return handle;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @happyvertical/smrt-web — browser client data runtime (#1761).\n *\n * A typed collection factory that materializes the manifest-generated web\n * collection definitions (`@happyvertical/smrt-virt-web`) as cached, reactive\n * collections over the generated SMRT REST surface.\n *\n * This package is the **engine-absorption boundary**: the client-data engine\n * (currently TanStack DB) is an implementation detail held entirely inside\n * this module. Its types never appear on the public API — collections are\n * handed back as the SMRT-owned {@link SmrtWebCollection}, and the shared cache\n * as the opaque {@link SmrtWebClient} — so the engine stays swappable without a\n * consumer-visible break. Consumers never import `@tanstack/*` directly.\n *\n * Framework-agnostic by construction: this entry imports no UI framework.\n * Svelte live-query bindings ship separately (see PRD #1755) so this core never\n * pulls the Svelte-only `@tanstack/svelte-db` export condition.\n *\n * Scope of this slice:\n * - stale-while-revalidate reads (a `staleTimeMs` window, background revalidation)\n * - concurrent-read dedup (one network request per in-flight collection load)\n * - optimistic create that persists through the generated REST surface and\n * rolls back automatically when the server errors\n * - relationship-derived invalidation (#1761): a settled mutation invalidates\n * the caches of the collections related to the mutated one, with the edges\n * derived from the manifest (`definition.relationships`) — no hand-wired\n * cache keys. Cross-collection reach requires a shared client from\n * {@link createSmrtWebClient}; with a private client only the mutated\n * collection refetches.\n * - hydration seeding (#1761): rows fetched server-side (a SvelteKit\n * `+page.server.ts` load) seed the shared cache via\n * {@link CreateSmrtCollectionOptions.initialData}, so the first client read\n * serves them WITHOUT a duplicate first-render fetch.\n *\n * Deliberately NOT here yet (see PRD #1755): offline outbox, SSE invalidation,\n * persistence, version awareness.\n */\n\nimport { createCollection } from '@tanstack/db';\nimport { QueryClient } from '@tanstack/query-core';\nimport { queryCollectionOptions } from '@tanstack/query-db-collection';\n\n// ---------------------------------------------------------------------------\n// Generated definition contract (mirrors @happyvertical/smrt-virt-web)\n// ---------------------------------------------------------------------------\n\n/**\n * Field metadata emitted per column by the `@happyvertical/smrt-virt-web`\n * virtual module (generated from the package manifest).\n */\nexport interface SmrtWebFieldDefinition {\n type: string;\n required?: boolean;\n default?: unknown;\n}\n\n/** The relationship kinds a generated web collection edge can describe. */\nexport type SmrtWebRelationshipKind =\n | 'foreignKey'\n | 'crossPackageRef'\n | 'oneToMany'\n | 'manyToMany';\n\n/**\n * A manifest-derived edge from this collection to a sibling REST collection,\n * emitted by the `@happyvertical/smrt-virt-web` virtual module. When a mutation\n * on this collection settles, the caches of the collections named by these\n * edges are invalidated (relationship-derived invalidation, #1761), so a\n * dependent view refetches without any hand-wired cache key.\n *\n * SMRT-owned data — no client-engine (`@tanstack/*`) type appears here, so it\n * stays inside the engine-absorption boundary.\n */\nexport interface SmrtWebRelationship {\n /** The declaring field carrying the relationship (e.g. `groupId`, `items`). */\n field: string;\n /** The relationship kind, mirroring the manifest field type. */\n kind: SmrtWebRelationshipKind;\n /** REST collection name the edge resolves to (e.g. `ad_groups`). */\n relatedCollection: string;\n}\n\n/**\n * One generated collection definition: everything needed to construct a client\n * collection over the generated REST surface. The `_row` property is a phantom\n * type carrier threaded through codegen — it never exists at runtime, it only\n * lets factories infer the row type from a definition.\n */\nexport interface SmrtWebCollectionDefinition<TData extends object = object> {\n /** REST collection name (e.g. `products`). */\n name: string;\n /** Source class name (e.g. `Product`). */\n className: string;\n /** Path under the API base path (e.g. `/products`). */\n endpoint: string;\n /** Primary key field name (`id` for SmrtObject). */\n idField: string;\n /** CRUD + custom actions exposed by the api decorator config. */\n actions: string[];\n /** Persisted field metadata keyed by field name. */\n fields: Record<string, SmrtWebFieldDefinition>;\n /**\n * Manifest-derived relationship edges to sibling REST collections. Drives\n * relationship-derived cache invalidation: a settled mutation on this\n * collection invalidates the caches of the collections these edges name.\n * Optional so hand-built definitions (older codegen, tests) still satisfy the\n * type; a missing value means \"no derived edges\".\n */\n relationships?: SmrtWebRelationship[];\n /** Phantom row-type carrier — never present at runtime. */\n _row?: TData;\n}\n\n// ---------------------------------------------------------------------------\n// Fetcher contract + payload normalization\n// ---------------------------------------------------------------------------\n\n/**\n * The per-collection CRUD surface of the generated REST client\n * (`createClient(basePath).<collection>` from `@happyvertical/smrt-virt-client`).\n *\n * Return types are `unknown` on purpose: generated fetchers resolve with\n * whatever the server sent, so this package normalizes and validates payloads\n * centrally — see {@link unwrapListResult} / {@link unwrapItemResult}.\n */\nexport interface SmrtCrudFetchers {\n list(params?: Record<string, unknown>): Promise<unknown>;\n get?(id: string): Promise<unknown>;\n create(data: Record<string, unknown>): Promise<unknown>;\n update?(id: string, data: Record<string, unknown>): Promise<unknown>;\n delete?(id: string): Promise<unknown>;\n}\n\n/**\n * Raised when a generated-client call resolved with an error payload\n * (`{ error: string }` from the generated REST routes) or an unexpected shape.\n * Thrown inside a mutation handler, this triggers the automatic rollback of\n * optimistic state.\n */\nexport class SmrtWebRequestError extends Error {\n readonly payload: unknown;\n\n constructor(message: string, payload?: unknown) {\n super(message);\n this.name = 'SmrtWebRequestError';\n this.payload = payload;\n }\n}\n\n/** A row as stored in the client collection: the DTO plus a required key. */\nexport type SmrtWebRow<TData extends object> = TData & { id: string };\n\n/**\n * Normalize a generated-client list result to an array of rows.\n *\n * The generated REST routes return a bare JSON array; `{ error }` payloads are\n * surfaced as failures. The `{ data: [...] }` envelope is tolerated for\n * ApiResponse-shaped clients (e.g. a mock client).\n */\nexport function unwrapListResult(\n result: unknown,\n collectionName: string,\n): Array<Record<string, unknown>> {\n if (Array.isArray(result)) {\n return result as Array<Record<string, unknown>>;\n }\n if (result && typeof result === 'object') {\n const record = result as Record<string, unknown>;\n if (typeof record.error === 'string') {\n throw new SmrtWebRequestError(\n `[smrt-web] list(${collectionName}) failed: ${record.error}`,\n result,\n );\n }\n if (Array.isArray(record.data)) {\n return record.data as Array<Record<string, unknown>>;\n }\n }\n throw new SmrtWebRequestError(\n `[smrt-web] list(${collectionName}) returned an unexpected payload shape`,\n result,\n );\n}\n\n/**\n * Normalize a generated-client item result (create/update) to a row.\n * `{ error }` payloads become failures — inside mutation handlers this is what\n * makes optimistic state roll back.\n */\nexport function unwrapItemResult(\n result: unknown,\n context: string,\n): Record<string, unknown> {\n if (result && typeof result === 'object' && !Array.isArray(result)) {\n const record = result as Record<string, unknown>;\n if (typeof record.error === 'string') {\n throw new SmrtWebRequestError(\n `[smrt-web] ${context} failed: ${record.error}`,\n result,\n );\n }\n if (\n record.data &&\n typeof record.data === 'object' &&\n !Array.isArray(record.data)\n ) {\n return record.data as Record<string, unknown>;\n }\n return record;\n }\n throw new SmrtWebRequestError(\n `[smrt-web] ${context} returned an unexpected payload shape`,\n result,\n );\n}\n\n/**\n * Build CRUD fetchers from a generated collection definition — the same URL\n * scheme and payload handling as the generated REST client\n * (`basePath + endpoint`), with one improvement: HTTP error statuses reject\n * with the server's `{ error }` body instead of resolving with it.\n */\nexport function createDefinitionFetchers(\n definition: SmrtWebCollectionDefinition<object>,\n basePath = '/api/v1',\n fetchFn: typeof fetch = (...args) => globalThis.fetch(...args),\n): SmrtCrudFetchers {\n const collectionUrl = `${basePath}${definition.endpoint}`;\n const headers = { 'Content-Type': 'application/json' };\n\n const parse = async (response: Response): Promise<unknown> => {\n const payload: unknown = await response.json().catch(() => null);\n if (!response.ok) {\n const message =\n payload &&\n typeof payload === 'object' &&\n typeof (payload as Record<string, unknown>).error === 'string'\n ? String((payload as Record<string, unknown>).error)\n : `HTTP ${response.status}`;\n throw new SmrtWebRequestError(\n `[smrt-web] ${definition.name} request failed: ${message}`,\n payload,\n );\n }\n return payload;\n };\n\n return {\n list: async () => parse(await fetchFn(collectionUrl, { headers })),\n get: async (id) =>\n parse(await fetchFn(`${collectionUrl}/${id}`, { headers })),\n create: async (data) =>\n parse(\n await fetchFn(collectionUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(data),\n }),\n ),\n update: async (id, data) =>\n parse(\n await fetchFn(`${collectionUrl}/${id}`, {\n method: 'PUT',\n headers,\n body: JSON.stringify(data),\n }),\n ),\n delete: async (id) => {\n const response = await fetchFn(`${collectionUrl}/${id}`, {\n method: 'DELETE',\n headers,\n });\n if (!response.ok) {\n throw new SmrtWebRequestError(\n `[smrt-web] delete(${definition.name}) failed: HTTP ${response.status}`,\n );\n }\n return true;\n },\n };\n}\n\n/**\n * Generate a client-local id for optimistic inserts. The generated REST layer\n * strips client-supplied ids on create (mass-assignment guard #1540), so this\n * id only identifies the optimistic row until the post-persist refetch swaps in\n * the server-assigned row.\n */\nexport function newLocalId(): string {\n const cryptoRef = globalThis.crypto as Crypto | undefined;\n if (cryptoRef?.randomUUID) {\n return cryptoRef.randomUUID();\n }\n return `local-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Engine-absorbing public surface (no @tanstack/* types leak past here)\n// ---------------------------------------------------------------------------\n\n/**\n * Opaque handle to the shared client cache / request-dedup layer. Create one\n * with {@link createSmrtWebClient} and pass the SAME instance to every\n * collection that should share a cache and deduplicate in-flight requests.\n *\n * The engine (currently a TanStack Query client) is intentionally hidden behind\n * this brand so it stays swappable — do not depend on its concrete shape.\n */\nexport interface SmrtWebClient {\n /** Phantom brand — this handle wraps the hidden client-cache engine. */\n readonly __smrtWebClient: 'SmrtWebClient';\n}\n\n/**\n * Engine-side shape of a {@link SmrtWebClient}. Never exported, so the engine\n * type never reaches the public surface. Extends the public brand so the value\n * created here carries the brand at runtime (enabling the validation below).\n */\ninterface SmrtWebClientEngine extends SmrtWebClient {\n readonly queryClient: QueryClient;\n}\n\n/**\n * Create a shared client-cache handle. Pass the returned handle as\n * {@link CreateSmrtCollectionOptions.client} to every collection that should\n * share a cache and deduplicate requests app-wide.\n */\nexport function createSmrtWebClient(): SmrtWebClient {\n const engine: SmrtWebClientEngine = {\n __smrtWebClient: 'SmrtWebClient',\n queryClient: new QueryClient(),\n };\n return engine;\n}\n\nfunction resolveQueryClient(client?: SmrtWebClient): QueryClient {\n if (!client) return new QueryClient();\n const engine = client as Partial<SmrtWebClientEngine>;\n if (engine.__smrtWebClient !== 'SmrtWebClient' || !engine.queryClient) {\n throw new SmrtWebRequestError(\n '[smrt-web] options.client must be a handle from createSmrtWebClient()',\n );\n }\n return engine.queryClient;\n}\n\n/**\n * Project an engine row to a plain public DTO. The client-data engine decorates\n * stored rows with enumerable virtual props (`$synced`/`$origin`/`$key`/\n * `$collectionId`) that would otherwise cross the SMRT boundary through spread\n * or JSON serialization. The `$` prefix is reserved for the engine; SMRT\n * columns never begin with it.\n */\nfunction toPlainRow<TData extends object>(row: unknown): SmrtWebRow<TData> {\n const plain: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row as Record<string, unknown>)) {\n if (key.charCodeAt(0) !== 36 /* '$' */) plain[key] = value;\n }\n return plain as SmrtWebRow<TData>;\n}\n\n/** Project the row values carried by a change notification to plain DTOs. */\nfunction projectChanges(changes: unknown): unknown {\n if (!Array.isArray(changes)) return changes;\n return changes.map((change) => {\n if (!change || typeof change !== 'object') return change;\n const record = change as Record<string, unknown>;\n const projected: Record<string, unknown> = { ...record };\n if (record.value && typeof record.value === 'object') {\n projected.value = toPlainRow(record.value);\n }\n if (record.previousValue && typeof record.previousValue === 'object') {\n projected.previousValue = toPlainRow(record.previousValue);\n }\n return projected;\n });\n}\n\n/**\n * A pending optimistic mutation. Await {@link isPersisted} to observe the\n * server outcome: it resolves once the write has been persisted through the\n * REST surface, and rejects (rolling the optimistic state back) on error.\n */\nexport interface SmrtWebTransaction {\n readonly isPersisted: { readonly promise: Promise<unknown> };\n}\n\n/** A change-subscription handle. Call {@link unsubscribe} to detach. */\nexport interface SmrtWebSubscription {\n unsubscribe(): void;\n}\n\n/**\n * A live, cached collection of plain-DTO rows — the SMRT-owned public contract\n * over the client-data engine. Exposes only the committed surface; the engine's\n * own type is never named here so it stays swappable.\n */\nexport interface SmrtWebCollection<TData extends object> {\n /** All rows currently in the collection (plain DTOs, insertion order). */\n readonly toArray: ReadonlyArray<SmrtWebRow<TData>>;\n /** Number of rows currently in the collection. */\n readonly size: number;\n /** True when a row with `key` is present. */\n has(key: string): boolean;\n /** The row with `key`, or `undefined`. */\n get(key: string): SmrtWebRow<TData> | undefined;\n /** Resolve once the first load has completed. */\n preload(): Promise<void>;\n /** Tear down subscriptions and cached state. */\n cleanup(): Promise<void>;\n /** Subscribe to change notifications; returns a detach handle. */\n subscribeChanges(callback: (changes: unknown) => void): SmrtWebSubscription;\n /**\n * Optimistically insert a row and persist it through the create fetcher. The\n * row is visible synchronously; the returned transaction settles on the\n * server outcome (see {@link SmrtWebTransaction}).\n */\n insert(row: SmrtWebRow<TData>): SmrtWebTransaction;\n}\n\n/**\n * Options for {@link createSmrtCollection}. Generic in the collection's row\n * type `TData` so {@link initialData} is checked against the same DTO the\n * collection stores; every other option is row-type-agnostic, so the parameter\n * defaults to `object` and can be omitted at call sites that pass no seed.\n */\nexport interface CreateSmrtCollectionOptions<TData extends object = object> {\n /**\n * Generated REST client surface for this collection, e.g.\n * `createClient('/api/v1').products` from the virt-client module. When\n * omitted, fetchers are derived from the definition's endpoint and `basePath`\n * with the same URL scheme and payload shapes the generated client uses.\n */\n fetchers?: SmrtCrudFetchers;\n /** API base path for definition-derived fetchers (default `/api/v1`). */\n basePath?: string;\n /** Fetch implementation override (tests, SSR). Defaults to global fetch. */\n fetchFn?: typeof fetch;\n /**\n * Shared cache handle from {@link createSmrtWebClient}. Pass one app-wide\n * instance so collections share a cache and deduplicate requests; a private\n * cache is created when omitted.\n */\n client?: SmrtWebClient;\n /**\n * Cache namespace for this collection's reads. Fold a backend / tenant /\n * preview discriminator in here when the SAME generated collection is\n * materialized against DIFFERENT backends while sharing one {@link client} —\n * without it those reads share a cache key and could serve one backend's rows\n * for the other for the whole `staleTimeMs` window. Omit for the common\n * single-backend case.\n */\n scope?: string;\n /**\n * Stale-while-revalidate window in milliseconds (default 30s): reads within\n * the window are served from the local collection without a network request;\n * the first read after it revalidates in the background.\n */\n staleTimeMs?: number;\n /** Retry failed loads (default false: fail fast, surface errors). */\n retry?: boolean;\n /**\n * Rows to seed this collection's cache with, before its first read — the\n * hydration path for server-rendered data (#1761). Fetch rows in a SvelteKit\n * `+page.server.ts` load, pass them here on the client, and the first read\n * serves them from cache WITHOUT a duplicate first-render network request\n * (SMRT-owned type, so no engine type appears on the option).\n *\n * The seed is written to the cache with a fresh timestamp, so it counts as\n * fresh for `staleTimeMs`: with the default window the first read does not\n * fetch, and the collection revalidates in the background only once the window\n * elapses (or immediately if `staleTimeMs` is 0). Seed the SAME rows the\n * server serialized so the pre- and post-hydration renders match.\n *\n * Seeds the SAME cache key the reads use — so with a shared {@link client},\n * fold the backend / tenant discriminator into {@link scope} to match, exactly\n * as reads do; otherwise one backend's seed would serve the other for the\n * `staleTimeMs` window.\n */\n initialData?: SmrtWebRow<TData>[];\n}\n\n/**\n * Registry mapping a public collection handle to its underlying engine\n * collection. Keyed weakly so a handle and its engine collection are collected\n * together. Read only through {@link getEngineCollection}.\n */\nconst engineCollections = new WeakMap<object, unknown>();\n\n/**\n * Retrieve the underlying engine collection backing a handle — an advanced\n * bridge for trusted framework bindings (e.g. the smrt-svelte live-query\n * binding), which must feed the engine collection to the query builder. Returns\n * `unknown` so no engine type crosses the boundary; callers cast. Throws for a\n * handle not produced by {@link createSmrtCollection}. Not needed for normal\n * use.\n */\nexport function getEngineCollection<TData extends object>(\n handle: SmrtWebCollection<TData>,\n): unknown {\n const engine = engineCollections.get(handle);\n if (engine === undefined) {\n throw new SmrtWebRequestError(\n '[smrt-web] getEngineCollection: not a smrt-web collection handle',\n );\n }\n return engine;\n}\n\n/**\n * Create a typed client collection over a generated SMRT collection definition\n * and the matching generated REST client fetchers.\n *\n * Reads: stale-while-revalidate. The first subscriber triggers a fetch;\n * re-subscribing within `staleTimeMs` serves local data with no request. N\n * concurrent identical reads coalesce into one network request.\n *\n * Writes: `collection.insert({ ...data, id: newLocalId() })` applies instantly,\n * persists through `fetchers.create()` (the temp id is stripped — the server\n * assigns the real one), then refetches to reconcile. A failed create rejects\n * the transaction and the optimistic row rolls back automatically.\n *\n * Relationship-derived invalidation: once a create/update/delete has persisted,\n * the query caches of this collection AND the collections named by\n * `definition.relationships` (manifest-derived edges) are invalidated, so\n * dependent views refetch. Reaching OTHER collections requires them to share\n * this collection's `client` (see {@link createSmrtWebClient}); with a private\n * client only this collection refetches.\n *\n * Hydration seeding: pass rows fetched server-side as\n * {@link CreateSmrtCollectionOptions.initialData} and the collection's first\n * read is served from them with NO network request (until `staleTimeMs`\n * elapses) — the SvelteKit `+page.server.ts` → hydrate path.\n */\nexport function createSmrtCollection<TData extends object>(\n definition: SmrtWebCollectionDefinition<TData>,\n options: CreateSmrtCollectionOptions<TData>,\n): SmrtWebCollection<TData> {\n type Row = SmrtWebRow<TData>;\n\n const { staleTimeMs = 30_000, retry = false, scope, initialData } = options;\n const fetchers =\n options.fetchers ??\n createDefinitionFetchers(definition, options.basePath, options.fetchFn);\n const queryClient = resolveQueryClient(options.client);\n const idField = definition.idField || 'id';\n\n // Scope discriminates the cache key so a shared client can materialize the\n // same collection against different backends without cross-serving reads.\n const cacheId = scope\n ? `smrt:${scope}:${definition.name}`\n : `smrt:${definition.name}`;\n const queryKey = scope\n ? ['smrt', scope, definition.name]\n : ['smrt', definition.name];\n\n // Hydration seeding (#1761): if the caller passed rows fetched server-side,\n // write them into the query cache BEFORE the collection's engine starts its\n // sync. On the first read the engine finds cached data and populates from it\n // instead of fetching (verified: zero list() calls). `setQueryData` stamps a\n // fresh `dataUpdatedAt`, so the seed counts as fresh for `staleTime` — the\n // first read serves it with no request, and revalidation fires only once\n // `staleTimeMs` elapses (or immediately when it is 0). An explicit empty seed\n // is honored too: it means \"the server returned zero rows\", a valid fresh\n // state that likewise suppresses the first fetch.\n //\n // Seed only when the key is empty, via the ATOMIC updater form: a plain\n // get-then-set would let two collections sharing this key and materialized in\n // the same tick both observe `undefined` and have the later seed clobber the\n // earlier one. `(existing) => existing ?? initialData` keeps the first seed\n // (or any already-cached rows, which may be newer than this late SSR payload)\n // in a single cache write.\n if (initialData !== undefined) {\n queryClient.setQueryData<Row[]>(\n queryKey,\n (existing) => existing ?? initialData,\n );\n }\n\n // Relationship-derived invalidation target set (#1761): the collections\n // whose caches a settled mutation on THIS collection must invalidate. Always\n // includes this collection itself (so its own read revalidates) plus every\n // manifest-derived related collection. Built once; a settled write matches\n // any cached query whose collection-name segment (the LAST queryKey element,\n // mirroring the `['smrt', (scope,) name]` scheme above) is in this set.\n //\n // Over-invalidation is safe — a stale query merely refetches. Under-\n // invalidation is the bug (a dependent view showing stale rows), so the\n // predicate matches by collection name across ALL scopes rather than an exact\n // key: a mutation in one scope refreshes the related collection in every\n // scope sharing the client.\n const invalidationTargets = new Set<string>([definition.name]);\n for (const relationship of definition.relationships ?? []) {\n invalidationTargets.add(relationship.relatedCollection);\n }\n\n /**\n * Invalidate the query caches of this collection and its manifest-derived\n * related collections. Cross-collection reach requires those collections to\n * share this collection's `client` (from {@link createSmrtWebClient}); with a\n * private client only THIS collection's query lives here, so only it\n * refetches. Fire-and-forget: invalidation schedules a background refetch and\n * must not delay the mutation's own settle.\n */\n const invalidateRelated = (): void => {\n void queryClient.invalidateQueries({\n predicate: (query) => {\n const key = query.queryKey;\n if (!Array.isArray(key) || key.length === 0) return false;\n const collectionSegment = key[key.length - 1];\n return (\n typeof collectionSegment === 'string' &&\n invalidationTargets.has(collectionSegment)\n );\n },\n });\n };\n\n const collection = createCollection(\n queryCollectionOptions<Row>({\n id: cacheId,\n queryKey,\n queryClient,\n staleTime: staleTimeMs,\n retry,\n queryFn: async () =>\n unwrapListResult(await fetchers.list(), definition.name) as Array<Row>,\n getKey: (row) => String((row as Record<string, unknown>)[idField]),\n onInsert: async ({ transaction }) => {\n for (const mutation of transaction.mutations) {\n const modified = mutation.modified as Record<string, unknown>;\n // Strip the client-local id: the generated REST layer rejects or\n // ignores client-supplied ids on create (#1540); the follow-up\n // refetch swaps the optimistic row for the server-assigned one.\n const { [idField]: _localId, ...data } = modified;\n unwrapItemResult(\n await fetchers.create(data),\n `create(${definition.name})`,\n );\n }\n // Persisted: refresh this collection and its related collections. Runs\n // only after every create resolved — a rejected create rolls the\n // optimistic row back and never reaches here.\n invalidateRelated();\n },\n onUpdate: fetchers.update\n ? async ({ transaction }) => {\n for (const mutation of transaction.mutations) {\n const key = String(mutation.key);\n const changes = mutation.changes as Record<string, unknown>;\n unwrapItemResult(\n // biome-ignore lint/style/noNonNullAssertion: guarded by the surrounding ternary\n await fetchers.update!(key, changes),\n `update(${definition.name})`,\n );\n }\n invalidateRelated();\n }\n : undefined,\n onDelete: fetchers.delete\n ? async ({ transaction }) => {\n for (const mutation of transaction.mutations) {\n // biome-ignore lint/style/noNonNullAssertion: guarded by the surrounding ternary\n await fetchers.delete!(String(mutation.key));\n }\n invalidateRelated();\n }\n : undefined,\n }),\n );\n\n // Wrap the engine collection in the SMRT-owned public surface. The wrapper\n // projects rows to plain DTOs at every read boundary (toArray/get and change\n // payloads) so the engine's virtual props never escape, and confines the\n // engine's own types to this module.\n const handle: SmrtWebCollection<TData> = {\n get toArray() {\n return collection.toArray.map((row) => toPlainRow<TData>(row));\n },\n get size() {\n return collection.size;\n },\n has(key) {\n return collection.has(key);\n },\n get(key) {\n const row = collection.get(key);\n return row === undefined ? undefined : toPlainRow<TData>(row);\n },\n preload() {\n return collection.preload();\n },\n cleanup() {\n return collection.cleanup();\n },\n subscribeChanges(callback) {\n const subscription = collection.subscribeChanges((changes: unknown) =>\n callback(projectChanges(changes)),\n );\n return { unsubscribe: () => subscription.unsubscribe() };\n },\n insert(row) {\n return collection.insert(row) as unknown as SmrtWebTransaction;\n },\n };\n\n engineCollections.set(handle, collection);\n return handle;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-web",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.11",
|
|
4
4
|
"description": "SMRT browser client data runtime: typed collection factory wrapping the client-data engine over generated REST clients",
|
|
5
5
|
"author": "HappyVertical",
|
|
6
6
|
"type": "module",
|