hono-takibi 0.9.78 → 0.9.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -85,10 +85,6 @@ export const getRoute = createRoute({
85
85
  })
86
86
  ```
87
87
 
88
- ### Demo
89
-
90
- ![](https://raw.githubusercontent.com/nakita628/hono-takibi/refs/heads/main/assets/demo/hono-takibi.gif)
91
-
92
88
  ## CLI
93
89
 
94
90
  ### Options
@@ -114,12 +110,6 @@ Options:
114
110
  -h, --help display help for command
115
111
  ```
116
112
 
117
- ### Example
118
-
119
- ```bash
120
- npx hono-takibi path/to/input.{yaml,json,tsp} -o path/to/output.ts --export-schemas --export-schemas-types --template --base-path '/api/v1'
121
- ```
122
-
123
113
  ## Configuration File (`hono-takibi.config.ts`)
124
114
 
125
115
  Config used by both the CLI and the Vite plugin.
@@ -206,19 +196,46 @@ export default defineConfig({
206
196
  },
207
197
  type: {
208
198
  output: './src/types.ts',
199
+ readonly: true,
209
200
  },
210
201
  rpc: {
211
202
  output: './src/rpc',
212
203
  import: '../client',
213
204
  split: true,
205
+ client: 'client',
206
+ },
207
+ // Client library integrations
208
+ 'tanstack-query': {
209
+ output: './src/tanstack-query',
210
+ import: '../client',
211
+ split: true,
212
+ client: 'client',
213
+ },
214
+ 'svelte-query': {
215
+ output: './src/svelte-query',
216
+ import: '../client',
217
+ split: true,
218
+ client: 'client',
219
+ },
220
+ swr: {
221
+ output: './src/swr',
222
+ import: '../client',
223
+ split: true,
224
+ client: 'client',
225
+ },
226
+ 'vue-query': {
227
+ output: './src/vue-query',
228
+ import: '../client',
229
+ split: true,
230
+ client: 'client',
214
231
  },
215
232
  })
216
233
  ```
217
234
 
218
- ## Essentials
235
+ ### Essentials
219
236
 
220
237
  * Put **`hono-takibi.config.ts`** at repo root.
221
- * Defaultexport with `defineConfig(...)`.
238
+ * Default-export with `defineConfig(...)`.
222
239
  * `input`: **`openapi.yaml`** (recommended), or `*.json` / `*.tsp`.
223
240
 
224
241
  > **About `split`**
@@ -226,49 +243,74 @@ export default defineConfig({
226
243
  > * `split: true` → `output` is a **directory**; many files + `index.ts`.
227
244
  > * `split` **omitted** or `false` → `output` is a **single `*.ts` file** (one file only).
228
245
 
229
- ---
230
-
231
- ## Single‑file
246
+ ## Client Library Integrations
232
247
 
233
- One file. Set top‑level `output` (don't define `components`/`routes`).
248
+ ### TanStack Query
234
249
 
235
250
  ```ts
236
- import { defineConfig } from 'hono-takibi/config'
237
-
238
251
  export default defineConfig({
239
252
  input: 'openapi.yaml',
240
- 'zod-openapi': {
241
- output: './src/index.ts',
242
- exportSchemas: true,
243
- exportSchemasTypes: true,
244
- },
253
+ 'zod-openapi': { output: './src/routes.ts', exportSchemas: true },
254
+ 'tanstack-query': { output: './src/hooks', import: '../client', split: true, client: 'client' },
245
255
  })
246
256
  ```
247
257
 
248
- ---
249
-
250
- ## RPC (optional)
251
-
252
- Works with either pattern.
253
-
254
- * `split: true` → `output` is a **directory**; many files + `index.ts`.
255
- * `split` **omitted** or `false` → `output` is **one `*.ts` file**.
258
+ Generated hooks (from Pet Store):
256
259
 
257
260
  ```ts
258
- import { defineConfig } from 'hono-takibi/config'
259
-
260
- export default defineConfig({
261
- input: 'openapi.yaml',
262
- 'zod-openapi': { output: './src/index.ts', exportSchemas: true, exportSchemasTypes: true },
263
- rpc: { output: './src/rpc', import: '../client', split: true },
261
+ import type {
262
+ QueryFunctionContext,
263
+ UseMutationOptions,
264
+ UseQueryOptions,
265
+ } from '@tanstack/react-query'
266
+ import { useMutation, useQuery } from '@tanstack/react-query'
267
+ import type { ClientRequestOptions, InferRequestType } from 'hono/client'
268
+ import { parseResponse } from 'hono/client'
269
+ import { client } from '../clients/pet-store'
270
+
271
+ /**
272
+ * Generates TanStack Query mutation key for PUT /pet
273
+ * Returns key ['prefix', 'method', 'path'] for mutation state tracking
274
+ */
275
+ export function getPutPetMutationKey() {
276
+ return ['pet', 'PUT', '/pet'] as const
277
+ }
278
+
279
+ /**
280
+ * Returns TanStack Query mutation options for PUT /pet
281
+ *
282
+ * Use with useMutation, setMutationDefaults, or isMutating.
283
+ */
284
+ export const getPutPetMutationOptions = (clientOptions?: ClientRequestOptions) => ({
285
+ mutationKey: getPutPetMutationKey(),
286
+ mutationFn: async (args: InferRequestType<typeof client.pet.$put>) =>
287
+ parseResponse(client.pet.$put(args, clientOptions)),
264
288
  })
265
- ```
266
289
 
267
- ---
290
+ /**
291
+ * PUT /pet
292
+ *
293
+ * Update an existing pet
294
+ *
295
+ * Update an existing pet by Id
296
+ */
297
+ export function usePutPet(options?: {
298
+ mutation?: UseMutationOptions<
299
+ Awaited<ReturnType<typeof parseResponse<Awaited<ReturnType<typeof client.pet.$put>>>>>,
300
+ Error,
301
+ InferRequestType<typeof client.pet.$put>
302
+ >
303
+ client?: ClientRequestOptions
304
+ }) {
305
+ const { mutation: mutationOptions, client: clientOptions } = options ?? {}
306
+ const { mutationKey, mutationFn, ...baseOptions } = getPutPetMutationOptions(clientOptions)
307
+ return useMutation({ ...baseOptions, ...mutationOptions, mutationKey, mutationFn })
308
+ }
309
+ ```
268
310
 
269
- ## Vite Plugin (`honoTakibiVite`)
311
+ ## Vite Plugin
270
312
 
271
- Auto‑regenerates on changes and reloads dev server.
313
+ Auto-regenerate on file changes:
272
314
 
273
315
  ```ts
274
316
  // vite.config.ts
@@ -280,41 +322,34 @@ export default defineConfig({
280
322
  })
281
323
  ```
282
324
 
283
- **What it does**
284
-
285
- * **Watches**: the config, your `input`, and nearby `**/*.{yaml,json,tsp}`.
286
- * **Generates** outputs per your config (single‑file or split, plus `rpc`).
287
- * **Cleans** old generated files safely when paths or `split` change.
288
-
289
- That’s it — set `input`, choose one of the two patterns, and (optionally) add `rpc`. ✅
290
-
291
- ### Demo (Vite + HMR)
292
-
293
325
  ![](https://raw.githubusercontent.com/nakita628/hono-takibi/refs/heads/main/assets/vite/hono-takibi-vite.gif)
294
326
 
295
- ### ⚠️ WARNING: Potential Breaking Changes Without Notice
327
+ ## Limitations
296
328
 
297
329
  **This package is in active development and may introduce breaking changes without prior notice.**
298
- Specifically:
330
+
331
+ - Not all OpenAPI features are supported
332
+ - Complex schemas might not convert correctly
333
+ - Some OpenAPI validations may not be perfectly converted to Zod
299
334
  - Schema generation logic might be updated
300
335
  - Output code structure could be modified
301
- - Example value handling might be altered
302
336
 
303
337
  We strongly recommend:
304
338
  - Pinning to exact versions in production
305
339
  - Testing thoroughly when updating versions
306
340
  - Reviewing generated code after updates
307
341
 
342
+ ## Contributing
343
+
308
344
  We welcome feedback and contributions to improve the tool!
309
345
 
310
- ### Current Limitations
346
+ If you find any issues with the generated code or have suggestions for improvements, please:
347
+
348
+ - Open an issue at [GitHub Issues](https://github.com/nakita628/hono-takibi/issues)
349
+ - Submit a pull request with your improvements
311
350
 
312
- **OpenAPI Support**
313
- - Not all OpenAPI features are supported
314
- - Complex schemas might not convert correctly
315
- - Limited support for certain response types
316
- - Some OpenAPI validations may not be perfectly converted to Zod validations
351
+ Your contributions help make this tool better for the community.
317
352
 
318
353
  ## License
319
354
 
320
- Distributed under the MIT License. See [LICENSE](https://github.com/nakita-Ypm/hono-takibi?tab=MIT-1-ov-file) for more information.
355
+ Distributed under the MIT License. See [LICENSE](https://github.com/nakita628/hono-takibi?tab=MIT-1-ov-file) for more information.
package/dist/cli/index.js CHANGED
@@ -22,7 +22,7 @@
22
22
  import { existsSync } from 'node:fs';
23
23
  import { resolve } from 'node:path';
24
24
  import { readConfig } from '../config/index.js';
25
- import { callbacks, examples, headers, links, parameters, requestBodies, responses, route, rpc, schemas, securitySchemes, takibi, type, } from '../core/index.js';
25
+ import { callbacks, examples, headers, links, parameters, requestBodies, responses, route, rpc, schemas, securitySchemes, svelteQuery, swr, takibi, tanstackQuery, type, vueQuery, } from '../core/index.js';
26
26
  import { parseOpenAPI } from '../openapi/index.js';
27
27
  const HELP_TEXT = `Usage: hono-takibi <input.{yaml,json,tsp}> -o <routes.ts> [options]
28
28
 
@@ -175,7 +175,7 @@ export async function honoTakibi() {
175
175
  if (!openAPIResult.ok)
176
176
  return { ok: false, error: openAPIResult.error };
177
177
  const openAPI = openAPIResult.value;
178
- const [takibiResult, schemaResult, parameterResult, headersResult, examplesResult, linksResult, callbacksResult, securitySchemesResult, requestBodiesResult, responsesResult, routeResult, typeResult, rpcResult,] = await Promise.all([
178
+ const [takibiResult, schemaResult, parameterResult, headersResult, examplesResult, linksResult, callbacksResult, securitySchemesResult, requestBodiesResult, responsesResult, routeResult, typeResult, rpcResult, swrResult, tanstackQueryResult, svelteQueryResult, vueQueryResult,] = await Promise.all([
179
179
  config['zod-openapi']?.output
180
180
  ? takibi(openAPI, config['zod-openapi'].output, false, false, '/', {
181
181
  readonly: config['zod-openapi'].readonly,
@@ -229,6 +229,18 @@ export async function honoTakibi() {
229
229
  config.rpc
230
230
  ? rpc(openAPI, config.rpc.output, config.rpc.import, config.rpc.split ?? false, config.rpc.client ?? 'client')
231
231
  : Promise.resolve(undefined),
232
+ config.swr
233
+ ? swr(openAPI, config.swr.output, config.swr.import, config.swr.split ?? false, config.swr.client ?? 'client')
234
+ : Promise.resolve(undefined),
235
+ config['tanstack-query']
236
+ ? tanstackQuery(openAPI, config['tanstack-query'].output, config['tanstack-query'].import, config['tanstack-query'].split ?? false, config['tanstack-query'].client ?? 'client')
237
+ : Promise.resolve(undefined),
238
+ config['svelte-query']
239
+ ? svelteQuery(openAPI, config['svelte-query'].output, config['svelte-query'].import, config['svelte-query'].split ?? false, config['svelte-query'].client ?? 'client')
240
+ : Promise.resolve(undefined),
241
+ config['vue-query']
242
+ ? vueQuery(openAPI, config['vue-query'].output, config['vue-query'].import, config['vue-query'].split ?? false, config['vue-query'].client ?? 'client')
243
+ : Promise.resolve(undefined),
232
244
  ]);
233
245
  if (takibiResult && !takibiResult.ok)
234
246
  return { ok: false, error: takibiResult.error };
@@ -256,6 +268,14 @@ export async function honoTakibi() {
256
268
  return { ok: false, error: typeResult.error };
257
269
  if (rpcResult && !rpcResult.ok)
258
270
  return { ok: false, error: rpcResult.error };
271
+ if (swrResult && !swrResult.ok)
272
+ return { ok: false, error: swrResult.error };
273
+ if (tanstackQueryResult && !tanstackQueryResult.ok)
274
+ return { ok: false, error: tanstackQueryResult.error };
275
+ if (svelteQueryResult && !svelteQueryResult.ok)
276
+ return { ok: false, error: svelteQueryResult.error };
277
+ if (vueQueryResult && !vueQueryResult.ok)
278
+ return { ok: false, error: vueQueryResult.error };
259
279
  const results = [
260
280
  takibiResult?.value,
261
281
  schemaResult?.value,
@@ -270,6 +290,10 @@ export async function honoTakibi() {
270
290
  routeResult?.value,
271
291
  typeResult?.value,
272
292
  rpcResult?.value,
293
+ swrResult?.value,
294
+ tanstackQueryResult?.value,
295
+ svelteQueryResult?.value,
296
+ vueQueryResult?.value,
273
297
  ].filter((v) => v !== undefined);
274
298
  return { ok: true, value: results.join('\n') };
275
299
  }
@@ -80,6 +80,30 @@ type Config = {
80
80
  readonly split?: boolean;
81
81
  readonly client?: string;
82
82
  };
83
+ readonly swr?: {
84
+ readonly output: string | `${string}.ts`;
85
+ readonly import: string;
86
+ readonly split?: boolean;
87
+ readonly client?: string;
88
+ };
89
+ readonly 'tanstack-query'?: {
90
+ readonly output: string | `${string}.ts`;
91
+ readonly import: string;
92
+ readonly split?: boolean;
93
+ readonly client?: string;
94
+ };
95
+ readonly 'svelte-query'?: {
96
+ readonly output: string | `${string}.ts`;
97
+ readonly import: string;
98
+ readonly split?: boolean;
99
+ readonly client?: string;
100
+ };
101
+ readonly 'vue-query'?: {
102
+ readonly output: string | `${string}.ts`;
103
+ readonly import: string;
104
+ readonly split?: boolean;
105
+ readonly client?: string;
106
+ };
83
107
  };
84
108
  /**
85
109
  * Validates and parses a hono-takibi configuration object.
@@ -210,6 +210,135 @@ export function parseConfig(config) {
210
210
  };
211
211
  }
212
212
  }
213
+ // swr
214
+ if (config.swr !== undefined) {
215
+ if (typeof config.swr.output !== 'string') {
216
+ return { ok: false, error: `Invalid output format for swr: ${String(config.swr.output)}` };
217
+ }
218
+ if (typeof config.swr.import !== 'string') {
219
+ return { ok: false, error: `Invalid import format for swr: ${String(config.swr.import)}` };
220
+ }
221
+ if (config.swr.split !== undefined && typeof config.swr.split !== 'boolean') {
222
+ return { ok: false, error: `Invalid split format for swr: ${String(config.swr.split)}` };
223
+ }
224
+ if (config.swr.client !== undefined && typeof config.swr.client !== 'string') {
225
+ return { ok: false, error: `Invalid client format for swr: ${String(config.swr.client)}` };
226
+ }
227
+ // split: true requires directory (no .ts)
228
+ if (config.swr.split === true && isTs(config.swr.output)) {
229
+ return {
230
+ ok: false,
231
+ error: `Invalid swr output path for split mode (must be a directory, not .ts): ${config.swr.output}`,
232
+ };
233
+ }
234
+ }
235
+ // tanstack-query
236
+ if (config['tanstack-query'] !== undefined) {
237
+ if (typeof config['tanstack-query'].output !== 'string') {
238
+ return {
239
+ ok: false,
240
+ error: `Invalid output format for tanstack-query: ${String(config['tanstack-query'].output)}`,
241
+ };
242
+ }
243
+ if (typeof config['tanstack-query'].import !== 'string') {
244
+ return {
245
+ ok: false,
246
+ error: `Invalid import format for tanstack-query: ${String(config['tanstack-query'].import)}`,
247
+ };
248
+ }
249
+ if (config['tanstack-query'].split !== undefined &&
250
+ typeof config['tanstack-query'].split !== 'boolean') {
251
+ return {
252
+ ok: false,
253
+ error: `Invalid split format for tanstack-query: ${String(config['tanstack-query'].split)}`,
254
+ };
255
+ }
256
+ if (config['tanstack-query'].client !== undefined &&
257
+ typeof config['tanstack-query'].client !== 'string') {
258
+ return {
259
+ ok: false,
260
+ error: `Invalid client format for tanstack-query: ${String(config['tanstack-query'].client)}`,
261
+ };
262
+ }
263
+ // split: true requires directory (no .ts)
264
+ if (config['tanstack-query'].split === true && isTs(config['tanstack-query'].output)) {
265
+ return {
266
+ ok: false,
267
+ error: `Invalid tanstack-query output path for split mode (must be a directory, not .ts): ${config['tanstack-query'].output}`,
268
+ };
269
+ }
270
+ }
271
+ // svelte-query
272
+ if (config['svelte-query'] !== undefined) {
273
+ if (typeof config['svelte-query'].output !== 'string') {
274
+ return {
275
+ ok: false,
276
+ error: `Invalid output format for svelte-query: ${String(config['svelte-query'].output)}`,
277
+ };
278
+ }
279
+ if (typeof config['svelte-query'].import !== 'string') {
280
+ return {
281
+ ok: false,
282
+ error: `Invalid import format for svelte-query: ${String(config['svelte-query'].import)}`,
283
+ };
284
+ }
285
+ if (config['svelte-query'].split !== undefined &&
286
+ typeof config['svelte-query'].split !== 'boolean') {
287
+ return {
288
+ ok: false,
289
+ error: `Invalid split format for svelte-query: ${String(config['svelte-query'].split)}`,
290
+ };
291
+ }
292
+ if (config['svelte-query'].client !== undefined &&
293
+ typeof config['svelte-query'].client !== 'string') {
294
+ return {
295
+ ok: false,
296
+ error: `Invalid client format for svelte-query: ${String(config['svelte-query'].client)}`,
297
+ };
298
+ }
299
+ // split: true requires directory (no .ts)
300
+ if (config['svelte-query'].split === true && isTs(config['svelte-query'].output)) {
301
+ return {
302
+ ok: false,
303
+ error: `Invalid svelte-query output path for split mode (must be a directory, not .ts): ${config['svelte-query'].output}`,
304
+ };
305
+ }
306
+ }
307
+ // vue-query
308
+ if (config['vue-query'] !== undefined) {
309
+ if (typeof config['vue-query'].output !== 'string') {
310
+ return {
311
+ ok: false,
312
+ error: `Invalid output format for vue-query: ${String(config['vue-query'].output)}`,
313
+ };
314
+ }
315
+ if (typeof config['vue-query'].import !== 'string') {
316
+ return {
317
+ ok: false,
318
+ error: `Invalid import format for vue-query: ${String(config['vue-query'].import)}`,
319
+ };
320
+ }
321
+ if (config['vue-query'].split !== undefined && typeof config['vue-query'].split !== 'boolean') {
322
+ return {
323
+ ok: false,
324
+ error: `Invalid split format for vue-query: ${String(config['vue-query'].split)}`,
325
+ };
326
+ }
327
+ if (config['vue-query'].client !== undefined &&
328
+ typeof config['vue-query'].client !== 'string') {
329
+ return {
330
+ ok: false,
331
+ error: `Invalid client format for vue-query: ${String(config['vue-query'].client)}`,
332
+ };
333
+ }
334
+ // split: true requires directory (no .ts)
335
+ if (config['vue-query'].split === true && isTs(config['vue-query'].output)) {
336
+ return {
337
+ ok: false,
338
+ error: `Invalid vue-query output path for split mode (must be a directory, not .ts): ${config['vue-query'].output}`,
339
+ };
340
+ }
341
+ }
213
342
  const result = {
214
343
  ...config,
215
344
  ...(config['zod-openapi'] && {
@@ -320,6 +449,38 @@ export function parseConfig(config) {
320
449
  : config.rpc.output,
321
450
  },
322
451
  }),
452
+ ...(config.swr && {
453
+ swr: {
454
+ ...config.swr,
455
+ output: config.swr.split !== true && !isTs(config.swr.output)
456
+ ? `${config.swr.output}/index.ts`
457
+ : config.swr.output,
458
+ },
459
+ }),
460
+ ...(config['tanstack-query'] && {
461
+ 'tanstack-query': {
462
+ ...config['tanstack-query'],
463
+ output: config['tanstack-query'].split !== true && !isTs(config['tanstack-query'].output)
464
+ ? `${config['tanstack-query'].output}/index.ts`
465
+ : config['tanstack-query'].output,
466
+ },
467
+ }),
468
+ ...(config['svelte-query'] && {
469
+ 'svelte-query': {
470
+ ...config['svelte-query'],
471
+ output: config['svelte-query'].split !== true && !isTs(config['svelte-query'].output)
472
+ ? `${config['svelte-query'].output}/index.ts`
473
+ : config['svelte-query'].output,
474
+ },
475
+ }),
476
+ ...(config['vue-query'] && {
477
+ 'vue-query': {
478
+ ...config['vue-query'],
479
+ output: config['vue-query'].split !== true && !isTs(config['vue-query'].output)
480
+ ? `${config['vue-query'].output}/index.ts`
481
+ : config['vue-query'].output,
482
+ },
483
+ }),
323
484
  };
324
485
  return {
325
486
  ok: true,
@@ -3,8 +3,13 @@
3
3
  *
4
4
  * @module core
5
5
  */
6
+ export { makeQueryHooks } from '../helper/query.js';
6
7
  export * from './components/index.js';
7
8
  export { route } from './route/index.js';
8
9
  export { rpc } from './rpc/index.js';
10
+ export { svelteQuery } from './svelte-query/index.js';
11
+ export { swr } from './swr/index.js';
9
12
  export { takibi } from './takibi/index.js';
13
+ export { tanstackQuery } from './tanstack-query/index.js';
10
14
  export { type } from './type/index.js';
15
+ export { vueQuery } from './vue-query/index.js';
@@ -3,9 +3,15 @@
3
3
  *
4
4
  * @module core
5
5
  */
6
+ // Shared query module
7
+ export { makeQueryHooks } from '../helper/query.js';
6
8
  export * from './components/index.js';
7
9
  // Generation functions
8
10
  export { route } from './route/index.js';
9
11
  export { rpc } from './rpc/index.js';
12
+ export { svelteQuery } from './svelte-query/index.js';
13
+ export { swr } from './swr/index.js';
10
14
  export { takibi } from './takibi/index.js';
15
+ export { tanstackQuery } from './tanstack-query/index.js';
11
16
  export { type } from './type/index.js';
17
+ export { vueQuery } from './vue-query/index.js';
@@ -1,4 +1,39 @@
1
1
  import type { OpenAPI } from '../../openapi/index.js';
2
+ /**
3
+ * Generates RPC client wrapper functions from OpenAPI specification.
4
+ *
5
+ * Creates type-safe client functions that wrap Hono RPC client calls,
6
+ * with proper TypeScript types derived from OpenAPI schemas.
7
+ *
8
+ * ```mermaid
9
+ * flowchart LR
10
+ * subgraph "Generated Code"
11
+ * A["export async function getUsers(params) { return await client.users.$get(params) }"]
12
+ * end
13
+ * subgraph "Usage"
14
+ * B["const users = await getUsers({ query: { limit: 10 } })"]
15
+ * end
16
+ * A --> B
17
+ * ```
18
+ *
19
+ * @param openAPI - Parsed OpenAPI specification
20
+ * @param output - Output file path or directory
21
+ * @param importPath - Import path for the Hono client
22
+ * @param split - Whether to split into multiple files (one per operation)
23
+ * @param clientName - Name of the client export (default: 'client')
24
+ * @returns Promise resolving to success message or error
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * // Single file output
29
+ * await rpc(openAPI, 'src/rpc.ts', './client')
30
+ * // Generates: src/rpc.ts with all RPC functions
31
+ *
32
+ * // Split mode output
33
+ * await rpc(openAPI, 'src/rpc', './client', true)
34
+ * // Generates: src/rpc/getUsers.ts, src/rpc/postUsers.ts, src/rpc/index.ts
35
+ * ```
36
+ */
2
37
  export declare function rpc(openAPI: OpenAPI, output: string | `${string}.ts`, importPath: string, split?: boolean, clientName?: string): Promise<{
3
38
  readonly ok: true;
4
39
  readonly value: string;