@neon/neon-js 0.0.0 → 0.10.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/supabase/index.ts"],"sourcesContent":["import {\n\tSupabaseAuthAdapter,\n\ttype SupabaseAuthAdapterInstance,\n\ttype SupabaseAuthAdapterOptions,\n} from \"@neon/auth/vanilla/adapters\";\nimport {\n\ttype CreateClientDataApiConfig,\n\ttype CreateClientResult,\n\tcreateClientImpl,\n} from \"../client/client-factory\";\nimport type { DeriveNeonUrls } from \"../client/derive-urls\";\nimport type { DefaultSchemaName, NeonClient } from \"../client/neon-client\";\n\n/**\n * `url` and `allowAnonymous` configure the client; every other key is forwarded to\n * `SupabaseAuthAdapter(...)`, e.g. `fetchOptions.headers.Cookie` to forward a request\n * cookie from a server.\n */\ntype SupabaseAuthConfig = SupabaseAuthAdapterOptions & {\n\t/** The auth service URL */\n\turl: string;\n\t/** Fall back to an anonymous token when there is no user session. */\n\tallowAnonymous?: boolean;\n};\n\ntype SupabaseDataApiConfig<SchemaName> = CreateClientDataApiConfig<\n\tSchemaName,\n\tSupabaseAuthAdapterInstance\n>;\n\n/** Configuration for the Supabase-compatible `createClient`. */\nexport type CreateSupabaseClientConfig<SchemaName> = {\n\tauth: SupabaseAuthConfig;\n\tdataApi: SupabaseDataApiConfig<SchemaName>;\n};\n\n/** Per-call overrides accepted alongside a single base URL. */\nexport type CreateSupabaseClientStringOptions<SchemaName> = {\n\tauth?: Partial<SupabaseAuthConfig>;\n\tdataApi?: Partial<SupabaseDataApiConfig<SchemaName>>;\n\tderiveUrls?: DeriveNeonUrls;\n};\n\n/**\n * `createClient` with the auth adapter fixed to `SupabaseAuthAdapter`, so `client.auth`\n * has the Supabase-compatible API (`signInWithPassword`, `onAuthStateChange`, ...).\n *\n * @example\n * ```typescript\n * import { createClient } from '@neon/neon-js/supabase';\n *\n * const client = createClient({\n * auth: { url: 'https://auth.example.com' },\n * dataApi: { url: 'https://data-api.example.com/rest/v1' },\n * });\n *\n * await client.auth.signInWithPassword({ email, password });\n * const { data: items } = await client.from('items').select();\n * ```\n */\n// biome-ignore lint/suspicious/noExplicitAny: `any` is the default so an unspecified Database generic stays compatible with the upstream PostgrestClient's untyped schema.\nexport function createClient<Database = any>(\n\tconfig: CreateSupabaseClientConfig<DefaultSchemaName<Database>>,\n): CreateClientResult<Database, SupabaseAuthAdapterInstance>;\n// biome-ignore lint/suspicious/noExplicitAny: `any` is the default so an unspecified Database generic stays compatible with the upstream PostgrestClient's untyped schema.\nexport function createClient<Database = any>(\n\tbaseUrl: string,\n\toptions?: CreateSupabaseClientStringOptions<DefaultSchemaName<Database>>,\n): CreateClientResult<Database, SupabaseAuthAdapterInstance>;\nexport function createClient<\n\t// biome-ignore lint/suspicious/noExplicitAny: `any` is the default so an unspecified Database generic stays compatible with the upstream PostgrestClient's untyped schema.\n\tDatabase = any,\n\tSchemaName extends string & keyof Database = DefaultSchemaName<Database>,\n>(\n\targ1: string | CreateSupabaseClientConfig<SchemaName>,\n\targ2?: CreateSupabaseClientStringOptions<SchemaName>,\n): NeonClient<Database, SchemaName, SupabaseAuthAdapterInstance> {\n\t// An `auth` block is always present here, so the implementation never takes its\n\t// external-auth branch and the result always carries `.auth`.\n\tconst client =\n\t\ttypeof arg1 === \"string\"\n\t\t\t? createClientImpl<\n\t\t\t\t\tDatabase,\n\t\t\t\t\tSchemaName,\n\t\t\t\t\tSupabaseAuthAdapterInstance\n\t\t\t\t>(arg1, { ...arg2, auth: withAdapter(arg2?.auth) })\n\t\t\t: createClientImpl<\n\t\t\t\t\tDatabase,\n\t\t\t\t\tSchemaName,\n\t\t\t\t\tSupabaseAuthAdapterInstance\n\t\t\t\t>({\n\t\t\t\t\t...arg1,\n\t\t\t\t\tauth: withAdapter(arg1.auth),\n\t\t\t\t});\n\treturn client as NeonClient<\n\t\tDatabase,\n\t\tSchemaName,\n\t\tSupabaseAuthAdapterInstance\n\t>;\n}\n\nfunction withAdapter<T extends Partial<SupabaseAuthConfig>>(\n\tauth: T | undefined,\n) {\n\tconst { url, allowAnonymous, ...adapterOptions } = auth ?? {};\n\treturn {\n\t\turl,\n\t\tallowAnonymous,\n\t\tadapter: SupabaseAuthAdapter(adapterOptions),\n\t} as Omit<T, keyof SupabaseAuthAdapterOptions> & {\n\t\tadapter: ReturnType<typeof SupabaseAuthAdapter>;\n\t};\n}\n"],"mappings":";;;AAqEA,SAAgB,aAKf,MACA,MACgE;CAkBhE,OAdC,OAAO,SAAS,WACb,iBAIC,MAAM;EAAE,GAAG;EAAM,MAAM,YAAY,MAAM,IAAI;CAAE,CAAC,IACjD,iBAIC;EACD,GAAG;EACH,MAAM,YAAY,KAAK,IAAI;CAC5B,CAAC;AAML;AAEA,SAAS,YACR,MACC;CACD,MAAM,EAAE,KAAK,gBAAgB,GAAG,mBAAmB,QAAQ,CAAC;CAC5D,OAAO;EACN;EACA;EACA,SAAS,oBAAoB,cAAc;CAC5C;AAGD"}
package/llms.txt ADDED
@@ -0,0 +1,397 @@
1
+ # @neon/neon-js
2
+
3
+ > The official TypeScript SDK for Neon - unified client combining authentication and PostgreSQL database querying with automatic token management.
4
+
5
+ `@neon/neon-js` brings together Neon Auth and Neon Data API in a single client. It provides a familiar interface for authentication and type-safe database queries with PostgREST.
6
+
7
+ ## When to Use This Package
8
+
9
+ | Use Case | Package |
10
+ |----------|---------|
11
+ | Auth + Database (most apps) | `@neon/neon-js` |
12
+ | Auth only (no database) | `@neon/auth` |
13
+ | Database only (external auth) | `@neon/postgrest-js` |
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @neon/neon-js
19
+ ```
20
+
21
+ ## Environment Variables
22
+
23
+ ```bash
24
+ # React/Vite
25
+ VITE_NEON_AUTH_URL=https://your-project.neon.tech/auth
26
+ VITE_NEON_DATA_API_URL=https://your-project.neon.tech/rest/v1
27
+
28
+ # Next.js
29
+ NEON_AUTH_BASE_URL=https://your-project.neon.tech
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```typescript
35
+ import { createClient } from '@neon/neon-js';
36
+
37
+ const client = createClient<Database>({
38
+ auth: {
39
+ url: import.meta.env.VITE_NEON_AUTH_URL,
40
+ },
41
+ dataApi: {
42
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
43
+ },
44
+ });
45
+
46
+ // Authenticate
47
+ await client.auth.signIn.email({
48
+ email: 'user@example.com',
49
+ password: 'secure-password',
50
+ });
51
+
52
+ // Query database (token automatically injected)
53
+ const { data: users } = await client
54
+ .from('users')
55
+ .select('*')
56
+ .eq('status', 'active');
57
+ ```
58
+
59
+ ## Authentication
60
+
61
+ ### Sign Up & Sign In
62
+
63
+ ```typescript
64
+ // Sign up
65
+ await client.auth.signUp.email({
66
+ email: 'user@example.com',
67
+ password: 'secure-password',
68
+ name: 'John Doe',
69
+ });
70
+
71
+ // Sign in with email
72
+ await client.auth.signIn.email({
73
+ email: 'user@example.com',
74
+ password: 'secure-password',
75
+ });
76
+
77
+ // Sign in with OAuth
78
+ await client.auth.signIn.social({
79
+ provider: 'google',
80
+ callbackURL: '/dashboard',
81
+ });
82
+
83
+ // Get session
84
+ const session = await client.auth.getSession();
85
+
86
+ // Sign out
87
+ await client.auth.signOut();
88
+ ```
89
+
90
+ ### Adapters
91
+
92
+ **Critical:** Adapters are factory functions - must call with `()`.
93
+
94
+ ```typescript
95
+ // Supabase-compatible API: import createClient from the /supabase subpath (no adapter option)
96
+ import { createClient } from '@neon/neon-js/supabase';
97
+
98
+ const client = createClient<Database>({
99
+ auth: {
100
+ url: import.meta.env.VITE_NEON_AUTH_URL,
101
+ },
102
+ dataApi: {
103
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
104
+ },
105
+ });
106
+
107
+ await client.auth.signInWithPassword({ email, password });
108
+ const { data: session } = await client.auth.getSession();
109
+
110
+ // Supabase adapter options (fetchOptions, sessionOptions, ...) go on the auth config, e.g. to forward a request cookie on a server:
111
+ // auth: { url, fetchOptions: { headers: { Cookie: request.headers.get('cookie') ?? '' } } }
112
+ ```
113
+
114
+ ```typescript
115
+ // BetterAuthVanillaAdapter - Direct Better Auth API
116
+ import { createClient, BetterAuthVanillaAdapter } from '@neon/neon-js';
117
+
118
+ const client = createClient<Database>({
119
+ auth: {
120
+ adapter: BetterAuthVanillaAdapter(),
121
+ url: import.meta.env.VITE_NEON_AUTH_URL,
122
+ },
123
+ dataApi: {
124
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
125
+ },
126
+ });
127
+
128
+ await client.auth.signIn.email({ email, password });
129
+ const session = await client.auth.getSession();
130
+ ```
131
+
132
+ ```typescript
133
+ // BetterAuthReactAdapter - React hooks (MUST use subpath import)
134
+ import { createClient } from '@neon/neon-js';
135
+ import { BetterAuthReactAdapter } from '@neon/neon-js/auth/react/adapters';
136
+
137
+ const client = createClient<Database>({
138
+ auth: {
139
+ adapter: BetterAuthReactAdapter(),
140
+ url: import.meta.env.VITE_NEON_AUTH_URL,
141
+ },
142
+ dataApi: {
143
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
144
+ },
145
+ });
146
+
147
+ function MyComponent() {
148
+ const session = client.auth.useSession();
149
+ if (session.isPending) return <div>Loading...</div>;
150
+ return <div>Hello, {session.data?.user.name}</div>;
151
+ }
152
+ ```
153
+
154
+ ### Anonymous Access
155
+
156
+ Enable RLS-based data access for unauthenticated users:
157
+
158
+ ```typescript
159
+ const client = createClient<Database>({
160
+ auth: {
161
+ url: import.meta.env.VITE_NEON_AUTH_URL,
162
+ allowAnonymous: true, // Enable anonymous data access via RLS
163
+ },
164
+ dataApi: {
165
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
166
+ },
167
+ });
168
+
169
+ // Works without signing in - uses anonymous token for RLS
170
+ const { data: publicItems } = await client.from('public_items').select();
171
+ ```
172
+
173
+ **Required SQL setup:**
174
+
175
+ ```sql
176
+ -- Enable RLS on table
177
+ ALTER TABLE public.public_items ENABLE ROW LEVEL SECURITY;
178
+
179
+ -- Allow anonymous read access
180
+ CREATE POLICY "anonymous_read" ON public.public_items
181
+ FOR SELECT TO anonymous USING (true);
182
+
183
+ -- Grant permissions to roles
184
+ GRANT SELECT ON public.public_items TO anonymous;
185
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.public_items TO authenticated;
186
+ ```
187
+
188
+ ## Next.js Integration
189
+
190
+ See [@neon/auth/NEXT-JS.md](https://github.com/neondatabase/neon-pkgs/blob/main/packages/auth/NEXT-JS.md) for full guide.
191
+
192
+ **Environment variables:**
193
+
194
+ ```bash
195
+ NEON_AUTH_BASE_URL=https://your-project.neon.tech
196
+ NEON_AUTH_COOKIE_SECRET=your-secret-at-least-32-characters-long
197
+ ```
198
+
199
+ **Quick setup:**
200
+
201
+ ```typescript
202
+ // lib/auth/server.ts - Unified server instance
203
+ import { createNeonAuth } from '@neon/neon-js/auth/next/server';
204
+
205
+ export const auth = createNeonAuth({
206
+ baseUrl: process.env.NEON_AUTH_BASE_URL!,
207
+ cookies: {
208
+ secret: process.env.NEON_AUTH_COOKIE_SECRET!,
209
+ sessionDataTtl: 300, // Optional: session data cache TTL in seconds (default: 300 = 5 min)
210
+ domain: '.example.com', // Optional: for cross-subdomain cookies
211
+ },
212
+ });
213
+
214
+ // app/api/auth/[...path]/route.ts - API handler
215
+ import { auth } from '@/lib/auth/server';
216
+ export const { GET, POST } = auth.handler();
217
+
218
+ // middleware.ts - Route protection
219
+ import { auth } from '@/lib/auth/server';
220
+ export default auth.middleware({ loginUrl: '/auth/sign-in' });
221
+
222
+ // lib/auth/client.ts - Client-side auth
223
+ "use client"
224
+ import { createAuthClient } from '@neon/neon-js/auth/next';
225
+ export const authClient = createAuthClient();
226
+
227
+ // Server Components
228
+ import { auth } from '@/lib/auth/server';
229
+ // Server components using `auth` methods must be rendered dynamically
230
+ export const dynamic = 'force-dynamic';
231
+ const { data: session } = await auth.getSession();
232
+
233
+ // Server Actions
234
+ import { auth } from '@/lib/auth/server';
235
+ await auth.signIn.email({ email, password });
236
+ ```
237
+
238
+ ## Database Querying
239
+
240
+ ### SELECT
241
+
242
+ ```typescript
243
+ // Simple select
244
+ const { data } = await client.from('users').select('id, name, email');
245
+
246
+ // With filters
247
+ const { data } = await client
248
+ .from('posts')
249
+ .select('*')
250
+ .eq('status', 'published')
251
+ .gt('views', 100)
252
+ .order('created_at', { ascending: false })
253
+ .limit(10);
254
+
255
+ // Joins
256
+ const { data } = await client
257
+ .from('posts')
258
+ .select(`
259
+ id,
260
+ title,
261
+ author:users(name, email)
262
+ `)
263
+ .eq('status', 'published');
264
+ ```
265
+
266
+ ### INSERT
267
+
268
+ ```typescript
269
+ // Single row
270
+ const { data } = await client
271
+ .from('users')
272
+ .insert({ name: 'Alice', email: 'alice@example.com' })
273
+ .select();
274
+
275
+ // Multiple rows
276
+ const { data } = await client
277
+ .from('users')
278
+ .insert([
279
+ { name: 'Bob', email: 'bob@example.com' },
280
+ { name: 'Carol', email: 'carol@example.com' },
281
+ ])
282
+ .select();
283
+ ```
284
+
285
+ ### UPDATE
286
+
287
+ ```typescript
288
+ const { data } = await client
289
+ .from('users')
290
+ .update({ status: 'inactive' })
291
+ .eq('last_login', null)
292
+ .select();
293
+ ```
294
+
295
+ ### DELETE
296
+
297
+ ```typescript
298
+ const { data } = await client
299
+ .from('users')
300
+ .delete()
301
+ .eq('status', 'deleted')
302
+ .select();
303
+ ```
304
+
305
+ ### RPC (Stored Procedures)
306
+
307
+ ```typescript
308
+ const { data } = await client.rpc('get_user_stats', {
309
+ user_id: 123,
310
+ start_date: '2024-01-01',
311
+ });
312
+ ```
313
+
314
+ ## Error Handling
315
+
316
+ ```typescript
317
+ import { AuthRequiredError } from '@neon/neon-js';
318
+
319
+ try {
320
+ const { data } = await client.from('posts').select();
321
+ } catch (error) {
322
+ if (error instanceof AuthRequiredError) {
323
+ // User not authenticated - redirect to login
324
+ window.location.href = '/login';
325
+ }
326
+ // Handle PostgREST errors
327
+ if (error.code === 'PGRST301') {
328
+ // Row-level security violation - check RLS policies
329
+ }
330
+ if (error.code === '42501') {
331
+ // Permission denied - check GRANT statements
332
+ }
333
+ }
334
+ ```
335
+
336
+ ## TypeScript
337
+
338
+ Pass a `Database` type generated from your schema to `createClient`:
339
+
340
+ ```typescript
341
+ import type { Database } from './types/database';
342
+
343
+ const client = createClient<Database>({
344
+ auth: { url: process.env.NEON_AUTH_URL! },
345
+ dataApi: { url: process.env.NEON_DATA_API_URL! },
346
+ });
347
+
348
+ // Fully typed queries with autocomplete
349
+ const { data } = await client
350
+ .from('users')
351
+ .select('id, name, email')
352
+ .eq('status', 'active');
353
+ ```
354
+
355
+ ## Configuration
356
+
357
+ ```typescript
358
+ const client = createClient({
359
+ auth: {
360
+ url: 'https://your-auth-server.neon.tech/auth',
361
+ allowAnonymous: true,
362
+ },
363
+ dataApi: {
364
+ url: 'https://your-data-api.neon.tech/rest/v1',
365
+ options: {
366
+ db: { schema: 'public' },
367
+ global: { headers: { 'X-Custom-Header': 'value' } },
368
+ },
369
+ },
370
+ });
371
+ ```
372
+
373
+ ## Critical Rules
374
+
375
+ 1. **Adapters are factories** - Call them: `BetterAuthVanillaAdapter()` not `BetterAuthVanillaAdapter`
376
+ 2. **Supabase API lives at `/supabase`** - `import { createClient } from '@neon/neon-js/supabase'`; the main `createClient` does not accept `SupabaseAuthAdapter`
377
+ 3. **React adapter import path** - Must use subpath: `@neon/neon-js/auth/react/adapters`
378
+ 4. **Anonymous access requires RLS** - Must configure database policies and grants
379
+ 5. **Type generation** - Regenerate your `Database` type after schema changes to keep types in sync
380
+
381
+ ## Troubleshooting
382
+
383
+ | Error | Cause | Fix |
384
+ |-------|-------|-----|
385
+ | `AuthRequiredError` | No session when querying | Sign in first or enable `allowAnonymous` |
386
+ | `permission denied for table` | RLS blocking access | Add RLS policy + GRANT for role |
387
+ | `adapter is not a function` | Missing `()` on adapter | Use `BetterAuthVanillaAdapter()` not `BetterAuthVanillaAdapter` |
388
+ | OAuth popup blocked | Browser blocking popup | User must allow popups for domain |
389
+ | `PGRST301` | JWT expired or invalid | Session expired - re-authenticate |
390
+ | Cross-tab sync not working | Node.js environment | BroadcastChannel is browser-only feature |
391
+ | Session always null | Cookies blocked | Check browser settings, try non-incognito |
392
+
393
+ ## Optional
394
+
395
+ - [PostgREST Documentation](https://postgrest.org): Query syntax reference
396
+ - [Better Auth Documentation](https://www.better-auth.com/docs): Authentication library docs
397
+ - [Next.js Integration Guide](https://github.com/neondatabase/neon-pkgs/blob/main/packages/auth/NEXT-JS.md): Full Next.js setup
package/package.json CHANGED
@@ -1,10 +1,114 @@
1
1
  {
2
2
  "name": "@neon/neon-js",
3
- "version": "0.0.0",
3
+ "version": "0.10.0",
4
+ "description": "TypeScript SDK for Neon Auth and Data API - authentication and PostgreSQL querying",
5
+ "keywords": [
6
+ "neon",
7
+ "neondb",
8
+ "postgresql",
9
+ "postgres",
10
+ "database",
11
+ "authentication",
12
+ "auth",
13
+ "typescript",
14
+ "sdk",
15
+ "better-auth",
16
+ "supabase",
17
+ "data-api"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/neondatabase/neon-pkgs.git",
22
+ "directory": "packages/neon-js"
23
+ },
24
+ "homepage": "https://github.com/neondatabase/neon-pkgs/tree/main/packages/neon-js#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/neondatabase/neon-pkgs/issues"
27
+ },
28
+ "license": "Apache-2.0",
29
+ "author": {
30
+ "name": "Neon",
31
+ "url": "https://neon.com"
32
+ },
33
+ "type": "module",
34
+ "sideEffects": false,
35
+ "main": "dist/index.js",
36
+ "types": "dist/index.d.ts",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.ts",
40
+ "default": "./dist/index.js"
41
+ },
42
+ "./supabase": {
43
+ "types": "./dist/supabase/index.d.ts",
44
+ "default": "./dist/supabase/index.js"
45
+ },
46
+ "./auth": {
47
+ "types": "./dist/auth/index.d.ts",
48
+ "default": "./dist/auth/index.js"
49
+ },
50
+ "./auth/types": {
51
+ "types": "./dist/auth/types/index.d.ts",
52
+ "default": "./dist/auth/types/index.js"
53
+ },
54
+ "./auth/react": {
55
+ "types": "./dist/auth/react/index.d.ts",
56
+ "default": "./dist/auth/react/index.js"
57
+ },
58
+ "./auth/react/adapters": {
59
+ "types": "./dist/auth/react/adapters/index.d.ts",
60
+ "default": "./dist/auth/react/adapters/index.js"
61
+ },
62
+ "./auth/vanilla": {
63
+ "types": "./dist/auth/vanilla/index.d.ts",
64
+ "default": "./dist/auth/vanilla/index.js"
65
+ },
66
+ "./auth/vanilla/adapters": {
67
+ "types": "./dist/auth/vanilla/adapters/index.d.ts",
68
+ "default": "./dist/auth/vanilla/adapters/index.js"
69
+ },
70
+ "./auth/next": {
71
+ "types": "./dist/auth/next/index.d.ts",
72
+ "default": "./dist/auth/next/index.js"
73
+ },
74
+ "./auth/next/server": {
75
+ "types": "./dist/auth/next/server/index.d.ts",
76
+ "default": "./dist/auth/next/server/index.js"
77
+ },
78
+ "./auth/server": {
79
+ "types": "./dist/auth/server/index.d.ts",
80
+ "default": "./dist/auth/server/index.js"
81
+ }
82
+ },
4
83
  "files": [
5
- "README.md"
84
+ "README.md",
85
+ "dist/",
86
+ "package.json",
87
+ "llms.txt"
6
88
  ],
89
+ "devDependencies": {
90
+ "@types/node": "^20.19.0",
91
+ "@vitest/coverage-v8": "^3.0.9",
92
+ "tsdown": "^0.14.1",
93
+ "typescript": "^5.9.0",
94
+ "vitest": "^3.0.9",
95
+ "@neon-internals/internal": "0.0.0"
96
+ },
97
+ "dependencies": {
98
+ "@neon/auth": "0.8.0",
99
+ "@neon/postgrest-js": "0.3.0"
100
+ },
101
+ "engines": {
102
+ "node": ">=20.19.0"
103
+ },
7
104
  "publishConfig": {
8
- "access": "public"
105
+ "provenance": false
106
+ },
107
+ "scripts": {
108
+ "build": "tsc --noEmit && tsdown",
109
+ "test": "vitest",
110
+ "test:node": "node --run test",
111
+ "test:ci": "vitest run --typecheck --passWithNoTests",
112
+ "tsc": "tsc"
9
113
  }
10
- }
114
+ }