{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table-nuqs",
  "title": "Data Table nuqs Store Adapter",
  "description": "URL-based state management adapter using nuqs for shareable, bookmarkable filter state.",
  "dependencies": [
    "nuqs"
  ],
  "registryDependencies": [
    "https://data-table.openstatus.dev/r/data-table.json"
  ],
  "files": [
    {
      "path": "src/lib/store/adapters/nuqs/index.ts",
      "content": "/**\n * nuqs Adapter for BYOS\n *\n * This adapter uses nuqs for URL-based state management.\n * It supports SSR and URL synchronization.\n */\n\n\"use client\";\n\nimport { useQueryStates, type ParserBuilder } from \"nuqs\";\nimport { useEffect, useMemo, useRef } from \"react\";\nimport type { InternalStoreAdapter } from \"../../adapter/types\";\nimport { getSchemaDefaults, validateState } from \"../../schema/serialization\";\nimport type { SchemaDefinition, StoreSnapshot } from \"../../schema/types\";\nimport { schemaToNuqsParsers } from \"./parser-bridge\";\nimport type { NuqsAdapterOptions } from \"./types\";\n\nexport type { NuqsAdapterOptions } from \"./types\";\n\n/**\n * Create a nuqs adapter for URL-based state management\n *\n * @example\n * ```typescript\n * const schema = createSchema({\n *   regions: field.array(field.string()).default([]),\n *   host: field.string(),\n * });\n *\n * function MyComponent() {\n *   const adapter = useNuqsAdapter(schema.definition, { id: 'my-table' });\n *   return (\n *     <DataTableStoreProvider adapter={adapter}>\n *       <DataTable />\n *     </DataTableStoreProvider>\n *   );\n * }\n * ```\n */\nexport function useNuqsAdapter<T extends Record<string, unknown>>(\n  schema: SchemaDefinition,\n  options: NuqsAdapterOptions<T>,\n): InternalStoreAdapter<T> {\n  const {\n    id,\n    initialState,\n    shallow = true,\n    history = \"push\",\n    scroll = false,\n    throttleMs = 50,\n  } = options;\n\n  const parsers = useMemo(() => schemaToNuqsParsers(schema), [schema]);\n  const defaults = useMemo(() => getSchemaDefaults(schema) as T, [schema]);\n\n  // Use nuqs hook\n  const [nuqsState, setNuqsState] = useQueryStates(\n    parsers as Record<string, ParserBuilder<unknown>>,\n    {\n      shallow,\n      history,\n      scroll,\n      throttleMs,\n    },\n  );\n\n  // Store state and version\n  const stateRef = useRef<T>(defaults);\n  const versionRef = useRef(0);\n  const listenersRef = useRef(new Set<() => void>());\n  const pausedRef = useRef(false);\n  const pendingStateRef = useRef<Partial<T> | null>(null);\n\n  // Cache server snapshot to avoid infinite loop with useSyncExternalStore\n  const serverSnapshotRef = useRef<StoreSnapshot<T>>({\n    state: { ...defaults, ...initialState } as T,\n    version: 0,\n  });\n\n  // Compute merged state once per render so validateState isn't called twice\n  const currentState = useMemo(() => {\n    const validated = validateState(schema, nuqsState) as T;\n    return { ...defaults, ...initialState, ...validated } as T;\n  }, [nuqsState, schema, defaults, initialState]);\n\n  // Sync ref synchronously so getSnapshot() is always current during this render\n  stateRef.current = currentState;\n\n  // Notify listeners when state changes\n  useEffect(() => {\n    stateRef.current = currentState;\n    versionRef.current++;\n    listenersRef.current.forEach((listener) => listener());\n  }, [currentState]);\n\n  // Create stable adapter reference\n  const adapter = useMemo<InternalStoreAdapter<T>>(() => {\n    return {\n      subscribe(listener: () => void) {\n        listenersRef.current.add(listener);\n        return () => {\n          listenersRef.current.delete(listener);\n        };\n      },\n\n      getSnapshot(): StoreSnapshot<T> {\n        return {\n          state: stateRef.current,\n          version: versionRef.current,\n        };\n      },\n\n      getServerSnapshot(): StoreSnapshot<T> {\n        // Return cached snapshot to avoid infinite loop with useSyncExternalStore\n        return serverSnapshotRef.current;\n      },\n\n      setState(partial: Partial<T>) {\n        if (pausedRef.current) {\n          pendingStateRef.current = {\n            ...pendingStateRef.current,\n            ...partial,\n          };\n          return;\n        }\n\n        // Convert undefined values to null for nuqs\n        const nuqsPartial: Record<string, unknown> = {};\n        for (const [key, value] of Object.entries(partial)) {\n          nuqsPartial[key] = value === undefined ? null : value;\n        }\n\n        setNuqsState((prev) => ({ ...prev, ...nuqsPartial }));\n      },\n\n      setField<K extends keyof T>(key: K, value: T[K]) {\n        this.setState({ [key]: value } as unknown as Partial<T>);\n      },\n\n      reset(fields?: (keyof T)[]) {\n        if (fields) {\n          const resetPartial: Partial<T> = {};\n          for (const field of fields) {\n            resetPartial[field] = defaults[field];\n          }\n          this.setState(resetPartial);\n        } else {\n          this.setState(defaults);\n        }\n      },\n\n      pause() {\n        pausedRef.current = true;\n      },\n\n      resume() {\n        pausedRef.current = false;\n        if (pendingStateRef.current) {\n          this.setState(pendingStateRef.current);\n          pendingStateRef.current = null;\n        }\n      },\n\n      isPaused() {\n        return pausedRef.current;\n      },\n\n      destroy() {\n        listenersRef.current.clear();\n      },\n\n      getTableId() {\n        return id;\n      },\n\n      getSchema() {\n        return schema;\n      },\n\n      getDefaults() {\n        return defaults;\n      },\n\n      // Internal methods for provider sync\n      _syncState(state: T) {\n        stateRef.current = state;\n        versionRef.current++;\n        listenersRef.current.forEach((listener) => listener());\n      },\n    };\n  }, [id, schema, defaults, initialState, setNuqsState]);\n\n  return adapter;\n}\n\n// Re-export parser bridge utilities for advanced use cases\nexport { schemaToNuqsParsers, createSchemaSerializer } from \"./parser-bridge\";\n\n// Re-export nuqs types for components that need them\n// This allows components to import from the adapter layer instead of nuqs directly\nexport type { ParserBuilder } from \"nuqs\";\n",
      "type": "registry:lib"
    },
    {
      "path": "src/lib/store/adapters/nuqs/types.ts",
      "content": "/**\n * nuqs Adapter Types\n */\n\nimport type { CreateAdapterOptions } from \"../../adapter/types\";\n\n/**\n * nuqs-specific adapter options\n */\nexport interface NuqsAdapterOptions<T extends Record<string, unknown>>\n  extends CreateAdapterOptions<T> {\n  /**\n   * Use shallow routing (default: true)\n   */\n  shallow?: boolean;\n\n  /**\n   * History mode: 'push' | 'replace' (default: 'push')\n   */\n  history?: \"push\" | \"replace\";\n\n  /**\n   * Scroll to top on change (default: false)\n   */\n  scroll?: boolean;\n\n  /**\n   * Throttle URL updates in milliseconds (default: 50)\n   */\n  throttleMs?: number;\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "src/lib/store/adapters/nuqs/server.ts",
      "content": "/**\n * nuqs Server Utilities for BYOS\n *\n * Server-side utilities for creating search params cache and serializers\n * from BYOS schema definitions. Import this from server components or API routes.\n *\n * @example\n * ```typescript\n * // In your schema file (e.g., search-params.ts)\n * import { createNuqsSearchParams } from '@/lib/store/adapters/nuqs/server';\n * import { filterSchema } from './schema';\n *\n * // Create search params utilities from schema\n * export const {\n *   searchParamsParser,\n *   searchParamsCache,\n *   searchParamsSerializer,\n * } = createNuqsSearchParams(filterSchema.definition, {\n *   // Add extra parsers for pagination, etc.\n *   extraParsers: {\n *     size: parseAsInteger.withDefault(40),\n *     cursor: parseAsTimestamp,\n *   },\n * });\n * ```\n */\n\nimport {\n  createSearchParamsCache,\n  createSerializer,\n  type ParserBuilder,\n} from \"nuqs/server\";\nimport type { SchemaDefinition } from \"../../schema/types\";\nimport { schemaToNuqsParsers, type SchemaToNuqsParsers } from \"./parser-bridge\";\n\nexport type { SchemaToNuqsParsers } from \"./parser-bridge\";\n\n/**\n * Options for creating nuqs search params utilities\n */\nexport interface CreateNuqsSearchParamsOptions<\n  TExtra extends Record<string, ParserBuilder<any>> = {},\n> {\n  /**\n   * Additional parsers to include (e.g., pagination params)\n   */\n  extraParsers?: TExtra;\n}\n\n/**\n * Result of createNuqsSearchParams\n */\nexport interface NuqsSearchParamsResult<\n  TSchema extends SchemaDefinition,\n  TExtra extends Record<string, ParserBuilder<any>> = {},\n> {\n  /**\n   * Combined parser object for useQueryStates\n   */\n  searchParamsParser: SchemaToNuqsParsers<TSchema> & TExtra;\n\n  /**\n   * Search params cache for server-side parsing\n   */\n  searchParamsCache: ReturnType<\n    typeof createSearchParamsCache<SchemaToNuqsParsers<TSchema> & TExtra>\n  >;\n\n  /**\n   * Serializer for converting state to URL string\n   */\n  searchParamsSerializer: ReturnType<\n    typeof createSerializer<SchemaToNuqsParsers<TSchema> & TExtra>\n  >;\n}\n\n/**\n * Create nuqs search params utilities from a BYOS schema\n *\n * This creates searchParamsParser, searchParamsCache, and searchParamsSerializer\n * from a schema definition, with optional extra parsers for pagination etc.\n *\n * @example\n * ```typescript\n * import { createNuqsSearchParams } from '@/lib/store/adapters/nuqs/server';\n * import { parseAsInteger, parseAsTimestamp } from 'nuqs/server';\n *\n * export const {\n *   searchParamsParser,\n *   searchParamsCache,\n *   searchParamsSerializer,\n * } = createNuqsSearchParams(filterSchema.definition, {\n *   extraParsers: {\n *     size: parseAsInteger.withDefault(40),\n *     start: parseAsInteger.withDefault(0),\n *     cursor: parseAsTimestamp,\n *   },\n * });\n * ```\n */\nexport function createNuqsSearchParams<\n  TSchema extends SchemaDefinition,\n  TExtra extends Record<string, ParserBuilder<any>> = {},\n>(\n  schema: TSchema,\n  options: CreateNuqsSearchParamsOptions<TExtra> = {},\n): NuqsSearchParamsResult<TSchema, TExtra> {\n  const { extraParsers = {} as TExtra } = options;\n\n  // Generate parsers from schema\n  const schemaParsers = schemaToNuqsParsers(schema);\n\n  // Combine with extra parsers\n  const searchParamsParser = {\n    ...schemaParsers,\n    ...extraParsers,\n  } as SchemaToNuqsParsers<TSchema> & TExtra;\n\n  // Create cache and serializer\n  const searchParamsCache = createSearchParamsCache(searchParamsParser);\n  const searchParamsSerializer = createSerializer(searchParamsParser);\n\n  return {\n    searchParamsParser,\n    searchParamsCache,\n    searchParamsSerializer,\n  };\n}\n\n// Re-export commonly used nuqs parsers for convenience\nexport {\n  parseAsArrayOf,\n  parseAsBoolean,\n  parseAsInteger,\n  parseAsString,\n  parseAsStringLiteral,\n  parseAsTimestamp,\n  createParser,\n  type ParserBuilder,\n  type inferParserType,\n} from \"nuqs/server\";\n\n// Re-export schema parser utilities\nexport { schemaToNuqsParsers, createSchemaSerializer } from \"./parser-bridge\";\n",
      "type": "registry:lib"
    },
    {
      "path": "src/lib/store/adapters/nuqs/parser-bridge.ts",
      "content": "/**\n * Parser Bridge - Converts BYOS schema to nuqs parsers\n *\n * This module bridges our schema field definitions to nuqs parser format.\n */\n\nimport { SORT_DELIMITER } from \"@/lib/delimiters\";\nimport {\n  createParser,\n  parseAsArrayOf,\n  parseAsBoolean,\n  parseAsInteger,\n  parseAsString,\n  parseAsStringLiteral,\n  parseAsTimestamp,\n  type ParserBuilder,\n} from \"nuqs/server\";\nimport type {\n  FieldBuilder,\n  FieldConfig,\n  SchemaDefinition,\n} from \"../../schema/types\";\n\n/**\n * Type mapping: Schema field type → nuqs ParserBuilder type\n */\nexport type SchemaToNuqsParsers<T extends SchemaDefinition> = {\n  [K in keyof T]: T[K] extends FieldBuilder<infer U>\n    ? ParserBuilder<U>\n    : ParserBuilder<unknown>;\n};\n\n/**\n * Convert a single field config to a nuqs parser\n */\nfunction fieldConfigToParser(\n  config: FieldConfig<unknown>,\n): ParserBuilder<unknown> {\n  switch (config.type) {\n    case \"string\":\n      return parseAsString as ParserBuilder<unknown>;\n\n    case \"number\":\n      return parseAsInteger as ParserBuilder<unknown>;\n\n    case \"boolean\":\n      return parseAsBoolean as ParserBuilder<unknown>;\n\n    case \"timestamp\":\n      return parseAsTimestamp as ParserBuilder<unknown>;\n\n    case \"stringLiteral\":\n      if (config.literals) {\n        return parseAsStringLiteral(\n          config.literals as unknown as readonly string[],\n        ) as ParserBuilder<unknown>;\n      }\n      return parseAsString as ParserBuilder<unknown>;\n\n    case \"array\":\n      if (config.itemConfig) {\n        const itemParser = fieldConfigToParser(config.itemConfig);\n        return parseAsArrayOf(\n          itemParser as ParserBuilder<string>,\n          config.delimiter,\n        ) as ParserBuilder<unknown>;\n      }\n      return parseAsArrayOf(\n        parseAsString,\n        config.delimiter,\n      ) as ParserBuilder<unknown>;\n\n    case \"sort\":\n      return createParser({\n        parse(queryValue: string) {\n          const [id, desc] = queryValue.split(SORT_DELIMITER);\n          if (!id) return null;\n          return { id, desc: desc === \"desc\" };\n        },\n        serialize(value: { id: string; desc: boolean }) {\n          return `${value.id}${SORT_DELIMITER}${value.desc ? \"desc\" : \"asc\"}`;\n        },\n      }) as ParserBuilder<unknown>;\n\n    default:\n      return parseAsString as ParserBuilder<unknown>;\n  }\n}\n\n/**\n * Apply default value to a nuqs parser\n */\nfunction applyDefault(\n  parser: ParserBuilder<unknown>,\n  defaultValue: unknown,\n): ParserBuilder<unknown> {\n  if (defaultValue !== null && defaultValue !== undefined) {\n    // Arrays default to empty array, not worth adding .withDefault\n    if (Array.isArray(defaultValue) && defaultValue.length === 0) {\n      return parser;\n    }\n    // Only apply withDefault for non-null primitive defaults\n    if (\n      typeof defaultValue === \"string\" ||\n      typeof defaultValue === \"number\" ||\n      typeof defaultValue === \"boolean\" ||\n      defaultValue instanceof Date\n    ) {\n      return (\n        parser as ParserBuilder<unknown> & {\n          withDefault: (d: unknown) => ParserBuilder<unknown>;\n        }\n      ).withDefault(defaultValue);\n    }\n  }\n  return parser;\n}\n\n/**\n * Convert a schema definition to nuqs parsers\n *\n * @example\n * ```typescript\n * const schema = createSchema({\n *   level: field.array(field.string()),\n *   latency: field.number(),\n * });\n *\n * // Type is inferred: { level: ParserBuilder<string[]>, latency: ParserBuilder<number> }\n * const parsers = schemaToNuqsParsers(schema.definition);\n * ```\n */\nexport function schemaToNuqsParsers<T extends SchemaDefinition>(\n  schema: T,\n): SchemaToNuqsParsers<T> {\n  const parsers: Record<string, ParserBuilder<unknown>> = {};\n\n  for (const [key, fieldBuilder] of Object.entries(schema)) {\n    const config = fieldBuilder._config;\n    let parser = fieldConfigToParser(config);\n    parser = applyDefault(parser, config.defaultValue);\n    parsers[key] = parser;\n  }\n\n  return parsers as SchemaToNuqsParsers<T>;\n}\n\n/**\n * Create a nuqs cache serializer from schema\n */\nexport function createSchemaSerializer(schema: SchemaDefinition) {\n  const parsers = schemaToNuqsParsers(schema);\n\n  return (state: Record<string, unknown>): string => {\n    const params = new URLSearchParams();\n\n    for (const [key, parser] of Object.entries(parsers)) {\n      const value = state[key];\n      if (value === null || value === undefined) continue;\n      if (Array.isArray(value) && value.length === 0) continue;\n\n      try {\n        const serialized = (\n          parser as { serialize?: (v: unknown) => string }\n        ).serialize?.(value);\n        if (serialized) {\n          params.set(key, serialized);\n        }\n      } catch {\n        // Skip invalid values\n      }\n    }\n\n    const str = params.toString();\n    return str ? `?${str}` : \"\";\n  };\n}\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:block"
}