@voltx/server 0.4.2 → 0.4.4
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/LICENSE +21 -0
- package/dist/index.cjs +87 -6
- package/dist/index.d.cts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +85 -5
- package/package.json +38 -17
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Promptly AI Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
CHANGED
|
@@ -40,7 +40,8 @@ __export(index_exports, {
|
|
|
40
40
|
filePathToUrlPath: () => filePathToUrlPath,
|
|
41
41
|
registerSSR: () => registerSSR,
|
|
42
42
|
registerStaticFiles: () => registerStaticFiles,
|
|
43
|
-
scanAndRegisterRoutes: () => scanAndRegisterRoutes
|
|
43
|
+
scanAndRegisterRoutes: () => scanAndRegisterRoutes,
|
|
44
|
+
voltxRouter: () => voltxRouter
|
|
44
45
|
});
|
|
45
46
|
module.exports = __toCommonJS(index_exports);
|
|
46
47
|
|
|
@@ -309,6 +310,86 @@ function createViteDevConfig(options = {}) {
|
|
|
309
310
|
};
|
|
310
311
|
}
|
|
311
312
|
|
|
313
|
+
// src/vite-plugin.ts
|
|
314
|
+
function voltxRouter(options = {}) {
|
|
315
|
+
const pagesDir = options.pagesDir ?? "src/pages";
|
|
316
|
+
const virtualModuleId = "virtual:voltx-routes";
|
|
317
|
+
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
|
318
|
+
return {
|
|
319
|
+
name: "voltx-router",
|
|
320
|
+
enforce: "pre",
|
|
321
|
+
config() {
|
|
322
|
+
return {
|
|
323
|
+
ssr: {
|
|
324
|
+
// react-router ships CJS — force Vite to bundle its .mjs for SSR
|
|
325
|
+
noExternal: ["react-router"]
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
resolveId(id) {
|
|
330
|
+
if (id === virtualModuleId) {
|
|
331
|
+
return resolvedVirtualModuleId;
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
load(id) {
|
|
335
|
+
if (id === resolvedVirtualModuleId) {
|
|
336
|
+
return `
|
|
337
|
+
import { createElement } from "react";
|
|
338
|
+
import { Routes, Route } from "react-router";
|
|
339
|
+
|
|
340
|
+
const pages = import.meta.glob("/${pagesDir}/**/*.tsx", { eager: true });
|
|
341
|
+
|
|
342
|
+
function buildRoutes() {
|
|
343
|
+
const routes = [];
|
|
344
|
+
for (const [filePath, mod] of Object.entries(pages)) {
|
|
345
|
+
const Component = mod.default;
|
|
346
|
+
if (!Component) continue;
|
|
347
|
+
|
|
348
|
+
let routePath = filePath
|
|
349
|
+
.replace("/${pagesDir}", "")
|
|
350
|
+
.replace(/\\.tsx$/, "")
|
|
351
|
+
.replace(/\\/index$/, "/")
|
|
352
|
+
.replace(/\\[([^\\]]+)\\]/g, ":$1");
|
|
353
|
+
|
|
354
|
+
if (!routePath.startsWith("/")) routePath = "/" + routePath;
|
|
355
|
+
if (routePath !== "/" && routePath.endsWith("/")) {
|
|
356
|
+
routePath = routePath.slice(0, -1);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
routes.push({ path: routePath, Component });
|
|
360
|
+
}
|
|
361
|
+
return routes;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const routes = buildRoutes();
|
|
365
|
+
|
|
366
|
+
export function VoltxRoutes() {
|
|
367
|
+
return createElement(
|
|
368
|
+
Routes,
|
|
369
|
+
null,
|
|
370
|
+
routes.map(({ path, Component }) =>
|
|
371
|
+
createElement(Route, { key: path, path, element: createElement(Component) })
|
|
372
|
+
)
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export { routes };
|
|
377
|
+
`;
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
// HMR: when a file in src/pages/ is added/removed, invalidate the virtual module
|
|
381
|
+
handleHotUpdate({ file, server }) {
|
|
382
|
+
if (file.includes(pagesDir.replace(/\//g, "/"))) {
|
|
383
|
+
const mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);
|
|
384
|
+
if (mod) {
|
|
385
|
+
server.moduleGraph.invalidateModule(mod);
|
|
386
|
+
server.ws.send({ type: "full-reload" });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
312
393
|
// src/ssr.ts
|
|
313
394
|
var import_node_path3 = require("path");
|
|
314
395
|
var import_node_fs = require("fs");
|
|
@@ -364,9 +445,8 @@ function registerSSR(app, vite, options = {}) {
|
|
|
364
445
|
try {
|
|
365
446
|
const { createServer: createViteServer } = await import("vite");
|
|
366
447
|
const devServer = await createViteServer({
|
|
367
|
-
server: { middlewareMode: true },
|
|
368
|
-
appType: "custom"
|
|
369
|
-
optimizeDeps: { disabled: true }
|
|
448
|
+
server: { middlewareMode: true, hmr: false },
|
|
449
|
+
appType: "custom"
|
|
370
450
|
});
|
|
371
451
|
const mod = await devServer.ssrLoadModule(entryServer);
|
|
372
452
|
render = mod.render;
|
|
@@ -450,7 +530,7 @@ ${cssLinks}
|
|
|
450
530
|
}
|
|
451
531
|
|
|
452
532
|
// src/index.ts
|
|
453
|
-
var VERSION = "0.4.
|
|
533
|
+
var VERSION = "0.4.4";
|
|
454
534
|
// Annotate the CommonJS export names for ESM import in node:
|
|
455
535
|
0 && (module.exports = {
|
|
456
536
|
Hono,
|
|
@@ -463,5 +543,6 @@ var VERSION = "0.4.2";
|
|
|
463
543
|
filePathToUrlPath,
|
|
464
544
|
registerSSR,
|
|
465
545
|
registerStaticFiles,
|
|
466
|
-
scanAndRegisterRoutes
|
|
546
|
+
scanAndRegisterRoutes,
|
|
547
|
+
voltxRouter
|
|
467
548
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as hono from 'hono';
|
|
2
2
|
import { Context, Hono } from 'hono';
|
|
3
3
|
export { Context, Hono } from 'hono';
|
|
4
|
+
import { Plugin } from 'vite';
|
|
4
5
|
|
|
5
6
|
interface ServerConfig {
|
|
6
7
|
/** Port to listen on (default: 3000) */
|
|
@@ -176,6 +177,24 @@ declare function createViteDevConfig(options?: ViteDevOptions): {
|
|
|
176
177
|
entry: string;
|
|
177
178
|
};
|
|
178
179
|
|
|
180
|
+
interface VoltxRouterOptions {
|
|
181
|
+
/** Directory to scan for page files (default: "src/pages") */
|
|
182
|
+
pagesDir?: string;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* VoltX file-based router plugin for Vite.
|
|
186
|
+
*
|
|
187
|
+
* Scans `src/pages/` and generates a virtual module that maps
|
|
188
|
+
* file paths to routes — just like Next.js.
|
|
189
|
+
*
|
|
190
|
+
* Convention:
|
|
191
|
+
* src/pages/index.tsx → /
|
|
192
|
+
* src/pages/about.tsx → /about
|
|
193
|
+
* src/pages/blog/index.tsx → /blog
|
|
194
|
+
* src/pages/blog/[slug].tsx → /blog/:slug
|
|
195
|
+
*/
|
|
196
|
+
declare function voltxRouter(options?: VoltxRouterOptions): Plugin;
|
|
197
|
+
|
|
179
198
|
interface SSROptions {
|
|
180
199
|
/** Path to entry-server module (default: src/entry-server.tsx) */
|
|
181
200
|
entryServer?: string;
|
|
@@ -223,6 +242,6 @@ interface ViteDevServer {
|
|
|
223
242
|
*/
|
|
224
243
|
declare function registerSSR(app: Hono, vite: ViteDevServer | null, options?: SSROptions): void;
|
|
225
244
|
|
|
226
|
-
declare const VERSION = "0.4.
|
|
245
|
+
declare const VERSION = "0.4.4";
|
|
227
246
|
|
|
228
|
-
export { type CorsConfig, type HttpMethod, type MiddlewareHandler, type RouteEntry, type RouteHandler, type RouteModule, type SSROptions, type ServerConfig, type ServerInfo, VERSION, type ViteDevOptions, type VoltxServer, createCorsMiddleware, createErrorHandler, createLoggerMiddleware, createServer, createViteDevConfig, filePathToUrlPath, registerSSR, registerStaticFiles, scanAndRegisterRoutes };
|
|
247
|
+
export { type CorsConfig, type HttpMethod, type MiddlewareHandler, type RouteEntry, type RouteHandler, type RouteModule, type SSROptions, type ServerConfig, type ServerInfo, VERSION, type ViteDevOptions, type VoltxRouterOptions, type VoltxServer, createCorsMiddleware, createErrorHandler, createLoggerMiddleware, createServer, createViteDevConfig, filePathToUrlPath, registerSSR, registerStaticFiles, scanAndRegisterRoutes, voltxRouter };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as hono from 'hono';
|
|
2
2
|
import { Context, Hono } from 'hono';
|
|
3
3
|
export { Context, Hono } from 'hono';
|
|
4
|
+
import { Plugin } from 'vite';
|
|
4
5
|
|
|
5
6
|
interface ServerConfig {
|
|
6
7
|
/** Port to listen on (default: 3000) */
|
|
@@ -176,6 +177,24 @@ declare function createViteDevConfig(options?: ViteDevOptions): {
|
|
|
176
177
|
entry: string;
|
|
177
178
|
};
|
|
178
179
|
|
|
180
|
+
interface VoltxRouterOptions {
|
|
181
|
+
/** Directory to scan for page files (default: "src/pages") */
|
|
182
|
+
pagesDir?: string;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* VoltX file-based router plugin for Vite.
|
|
186
|
+
*
|
|
187
|
+
* Scans `src/pages/` and generates a virtual module that maps
|
|
188
|
+
* file paths to routes — just like Next.js.
|
|
189
|
+
*
|
|
190
|
+
* Convention:
|
|
191
|
+
* src/pages/index.tsx → /
|
|
192
|
+
* src/pages/about.tsx → /about
|
|
193
|
+
* src/pages/blog/index.tsx → /blog
|
|
194
|
+
* src/pages/blog/[slug].tsx → /blog/:slug
|
|
195
|
+
*/
|
|
196
|
+
declare function voltxRouter(options?: VoltxRouterOptions): Plugin;
|
|
197
|
+
|
|
179
198
|
interface SSROptions {
|
|
180
199
|
/** Path to entry-server module (default: src/entry-server.tsx) */
|
|
181
200
|
entryServer?: string;
|
|
@@ -223,6 +242,6 @@ interface ViteDevServer {
|
|
|
223
242
|
*/
|
|
224
243
|
declare function registerSSR(app: Hono, vite: ViteDevServer | null, options?: SSROptions): void;
|
|
225
244
|
|
|
226
|
-
declare const VERSION = "0.4.
|
|
245
|
+
declare const VERSION = "0.4.4";
|
|
227
246
|
|
|
228
|
-
export { type CorsConfig, type HttpMethod, type MiddlewareHandler, type RouteEntry, type RouteHandler, type RouteModule, type SSROptions, type ServerConfig, type ServerInfo, VERSION, type ViteDevOptions, type VoltxServer, createCorsMiddleware, createErrorHandler, createLoggerMiddleware, createServer, createViteDevConfig, filePathToUrlPath, registerSSR, registerStaticFiles, scanAndRegisterRoutes };
|
|
247
|
+
export { type CorsConfig, type HttpMethod, type MiddlewareHandler, type RouteEntry, type RouteHandler, type RouteModule, type SSROptions, type ServerConfig, type ServerInfo, VERSION, type ViteDevOptions, type VoltxRouterOptions, type VoltxServer, createCorsMiddleware, createErrorHandler, createLoggerMiddleware, createServer, createViteDevConfig, filePathToUrlPath, registerSSR, registerStaticFiles, scanAndRegisterRoutes, voltxRouter };
|
package/dist/index.js
CHANGED
|
@@ -263,6 +263,86 @@ function createViteDevConfig(options = {}) {
|
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
// src/vite-plugin.ts
|
|
267
|
+
function voltxRouter(options = {}) {
|
|
268
|
+
const pagesDir = options.pagesDir ?? "src/pages";
|
|
269
|
+
const virtualModuleId = "virtual:voltx-routes";
|
|
270
|
+
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
|
271
|
+
return {
|
|
272
|
+
name: "voltx-router",
|
|
273
|
+
enforce: "pre",
|
|
274
|
+
config() {
|
|
275
|
+
return {
|
|
276
|
+
ssr: {
|
|
277
|
+
// react-router ships CJS — force Vite to bundle its .mjs for SSR
|
|
278
|
+
noExternal: ["react-router"]
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
},
|
|
282
|
+
resolveId(id) {
|
|
283
|
+
if (id === virtualModuleId) {
|
|
284
|
+
return resolvedVirtualModuleId;
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
load(id) {
|
|
288
|
+
if (id === resolvedVirtualModuleId) {
|
|
289
|
+
return `
|
|
290
|
+
import { createElement } from "react";
|
|
291
|
+
import { Routes, Route } from "react-router";
|
|
292
|
+
|
|
293
|
+
const pages = import.meta.glob("/${pagesDir}/**/*.tsx", { eager: true });
|
|
294
|
+
|
|
295
|
+
function buildRoutes() {
|
|
296
|
+
const routes = [];
|
|
297
|
+
for (const [filePath, mod] of Object.entries(pages)) {
|
|
298
|
+
const Component = mod.default;
|
|
299
|
+
if (!Component) continue;
|
|
300
|
+
|
|
301
|
+
let routePath = filePath
|
|
302
|
+
.replace("/${pagesDir}", "")
|
|
303
|
+
.replace(/\\.tsx$/, "")
|
|
304
|
+
.replace(/\\/index$/, "/")
|
|
305
|
+
.replace(/\\[([^\\]]+)\\]/g, ":$1");
|
|
306
|
+
|
|
307
|
+
if (!routePath.startsWith("/")) routePath = "/" + routePath;
|
|
308
|
+
if (routePath !== "/" && routePath.endsWith("/")) {
|
|
309
|
+
routePath = routePath.slice(0, -1);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
routes.push({ path: routePath, Component });
|
|
313
|
+
}
|
|
314
|
+
return routes;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const routes = buildRoutes();
|
|
318
|
+
|
|
319
|
+
export function VoltxRoutes() {
|
|
320
|
+
return createElement(
|
|
321
|
+
Routes,
|
|
322
|
+
null,
|
|
323
|
+
routes.map(({ path, Component }) =>
|
|
324
|
+
createElement(Route, { key: path, path, element: createElement(Component) })
|
|
325
|
+
)
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export { routes };
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
// HMR: when a file in src/pages/ is added/removed, invalidate the virtual module
|
|
334
|
+
handleHotUpdate({ file, server }) {
|
|
335
|
+
if (file.includes(pagesDir.replace(/\//g, "/"))) {
|
|
336
|
+
const mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);
|
|
337
|
+
if (mod) {
|
|
338
|
+
server.moduleGraph.invalidateModule(mod);
|
|
339
|
+
server.ws.send({ type: "full-reload" });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
266
346
|
// src/ssr.ts
|
|
267
347
|
import { resolve as resolve2 } from "path";
|
|
268
348
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -318,9 +398,8 @@ function registerSSR(app, vite, options = {}) {
|
|
|
318
398
|
try {
|
|
319
399
|
const { createServer: createViteServer } = await import("vite");
|
|
320
400
|
const devServer = await createViteServer({
|
|
321
|
-
server: { middlewareMode: true },
|
|
322
|
-
appType: "custom"
|
|
323
|
-
optimizeDeps: { disabled: true }
|
|
401
|
+
server: { middlewareMode: true, hmr: false },
|
|
402
|
+
appType: "custom"
|
|
324
403
|
});
|
|
325
404
|
const mod = await devServer.ssrLoadModule(entryServer);
|
|
326
405
|
render = mod.render;
|
|
@@ -404,7 +483,7 @@ ${cssLinks}
|
|
|
404
483
|
}
|
|
405
484
|
|
|
406
485
|
// src/index.ts
|
|
407
|
-
var VERSION = "0.4.
|
|
486
|
+
var VERSION = "0.4.4";
|
|
408
487
|
export {
|
|
409
488
|
Hono2 as Hono,
|
|
410
489
|
VERSION,
|
|
@@ -416,5 +495,6 @@ export {
|
|
|
416
495
|
filePathToUrlPath,
|
|
417
496
|
registerSSR,
|
|
418
497
|
registerStaticFiles,
|
|
419
|
-
scanAndRegisterRoutes
|
|
498
|
+
scanAndRegisterRoutes,
|
|
499
|
+
voltxRouter
|
|
420
500
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltx/server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "VoltX Server — Hono-based HTTP server with file-based routing, SSE streaming, and static file serving",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -13,31 +13,47 @@
|
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
-
"files": [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
20
|
-
"clean": "rm -rf dist"
|
|
21
|
-
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
22
19
|
"dependencies": {
|
|
23
|
-
"hono": "^
|
|
24
|
-
"
|
|
20
|
+
"@hono/node-server": "^1.14.0",
|
|
21
|
+
"hono": "^4.7.0"
|
|
25
22
|
},
|
|
26
23
|
"peerDependencies": {
|
|
27
24
|
"react": ">=18.0.0",
|
|
28
|
-
"react-dom": ">=18.0.0"
|
|
25
|
+
"react-dom": ">=18.0.0",
|
|
26
|
+
"vite": ">=5.0.0"
|
|
29
27
|
},
|
|
30
28
|
"peerDependenciesMeta": {
|
|
31
|
-
"react": {
|
|
32
|
-
|
|
29
|
+
"react": {
|
|
30
|
+
"optional": true
|
|
31
|
+
},
|
|
32
|
+
"react-dom": {
|
|
33
|
+
"optional": true
|
|
34
|
+
},
|
|
35
|
+
"vite": {
|
|
36
|
+
"optional": true
|
|
37
|
+
}
|
|
33
38
|
},
|
|
34
39
|
"devDependencies": {
|
|
40
|
+
"@types/react": "^19.0.0",
|
|
41
|
+
"@types/react-dom": "^19.0.0",
|
|
35
42
|
"tsup": "^8.0.0",
|
|
36
43
|
"typescript": "^5.7.0",
|
|
37
|
-
"
|
|
38
|
-
"@types/react-dom": "^19.0.0"
|
|
44
|
+
"vite": "^8.0.1"
|
|
39
45
|
},
|
|
40
|
-
"keywords": [
|
|
46
|
+
"keywords": [
|
|
47
|
+
"voltx",
|
|
48
|
+
"server",
|
|
49
|
+
"hono",
|
|
50
|
+
"http",
|
|
51
|
+
"routing",
|
|
52
|
+
"file-based",
|
|
53
|
+
"sse",
|
|
54
|
+
"streaming",
|
|
55
|
+
"middleware"
|
|
56
|
+
],
|
|
41
57
|
"license": "MIT",
|
|
42
58
|
"repository": {
|
|
43
59
|
"type": "git",
|
|
@@ -45,5 +61,10 @@
|
|
|
45
61
|
"directory": "packages/server"
|
|
46
62
|
},
|
|
47
63
|
"homepage": "https://voltx.co.in",
|
|
48
|
-
"author": "Promptly AI Team"
|
|
49
|
-
|
|
64
|
+
"author": "Promptly AI Team",
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean --external vite",
|
|
67
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
68
|
+
"clean": "rm -rf dist"
|
|
69
|
+
}
|
|
70
|
+
}
|