@amamo/mdx 0.1.6 → 0.3.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.
package/README.md CHANGED
@@ -1,50 +1,75 @@
1
1
  # @amamo/mdx
2
2
 
3
- Compile trusted MDX into JavaScript modules for a configurable JSX runtime (React by default),
4
- collection metadata, and JSON manifests. A Rust native binding handles parsing, validation, media
5
- rewriting, manifest projection, and persistent cache records; Shiki runs in JavaScript and feeds
6
- highlighted HAST back into the same compile pipeline.
3
+ Build MDX collections for Vite 8, Next 16, or a custom Node.js build. Define each collection with
4
+ the package's `z` schema builder, then import MDX as application modules or consume the generated
5
+ collection registry and JSON manifests.
7
6
 
8
- The package exposes three import paths:
7
+ `@amamo/mdx` provides:
9
8
 
10
- | Import | Purpose |
11
- | ----------------- | -------------------------------------------------- |
12
- | `@amamo/mdx` | Configure and drive the compiler directly. |
13
- | `@amamo/mdx/vite` | Compile MDX through Vite 8. |
14
- | `@amamo/mdx/next` | Compile MDX for Next 16 with Turbopack or Webpack. |
9
+ - frontmatter validation and defaults;
10
+ - JavaScript modules for a configurable JSX runtime, with React as the default;
11
+ - fenced-code highlighting and Markdown media imports;
12
+ - collection metadata with companion TypeScript declarations;
13
+ - configurable JSON manifests and a persistent build cache.
15
14
 
16
- ## Quick start
15
+ ## Requirements
16
+
17
+ - Node.js 20.19 or newer.
18
+ - A [supported native target](https://jikkai.github.io/mdx/native-targets/). There is no JavaScript
19
+ or WASI fallback for MDX compilation.
20
+ - React 19 when using the default JSX runtime.
21
+
22
+ MDX can contain imports, expressions, and JSX. Compile content from authors who are allowed to add
23
+ application code.
24
+
25
+ ## Install
17
26
 
18
27
  ```sh
19
28
  pnpm add @amamo/mdx
20
29
  ```
21
30
 
22
- `@amamo/mdx` requires Node.js 20.19 or newer and a [supported native
23
- target](https://jikkai.github.io/mdx/native-targets/). It has no JavaScript or WASI fallback.
31
+ The package manager installs the platform package for the current operating system and CPU. Install
32
+ dependencies again after moving the project to a different platform instead of copying
33
+ `node_modules`.
34
+
35
+ ## Define a collection
24
36
 
25
- Create a serializable config:
37
+ Create `amamo.config.mjs`:
26
38
 
27
39
  ```js
28
- // amamo.config.mjs
29
- import { defineConfig } from '@amamo/mdx'
40
+ import { defineConfig, z } from '@amamo/mdx'
30
41
 
31
42
  export default defineConfig({
32
43
  root: import.meta.dirname,
33
44
  collections: {
34
45
  posts: {
35
46
  directory: 'content/posts',
36
- schema: {
37
- $schema: 'https://json-schema.org/draft/2020-12/schema',
38
- type: 'object',
39
- properties: { title: { type: 'string' } },
40
- required: ['title'],
41
- },
47
+ schema: z.object({
48
+ title: z.string(),
49
+ publishedAt: z.string().optional(),
50
+ }),
42
51
  },
43
52
  },
44
53
  })
45
54
  ```
46
55
 
47
- Then choose the integration that owns the build.
56
+ Then add `content/posts/hello.mdx`:
57
+
58
+ ```mdx
59
+ ---
60
+ title: Hello
61
+ publishedAt: 2026-08-15
62
+ ---
63
+
64
+ # Hello
65
+
66
+ This document is compiled by @amamo/mdx.
67
+ ```
68
+
69
+ The collection directory must exist before the first full build. Relative collection, cache,
70
+ generated, and manifest paths are resolved from `root`.
71
+
72
+ ## Choose an integration
48
73
 
49
74
  ### Vite
50
75
 
@@ -55,7 +80,9 @@ import { defineConfig } from 'vite'
55
80
 
56
81
  import amamo from './amamo.config.mjs'
57
82
 
58
- export default defineConfig({ plugins: [amamoMdx(amamo)] })
83
+ export default defineConfig({
84
+ plugins: [amamoMdx(amamo)],
85
+ })
59
86
  ```
60
87
 
61
88
  ### Next
@@ -66,17 +93,21 @@ import { withAmamoMdx } from '@amamo/mdx/next'
66
93
 
67
94
  import amamo from './amamo.config.mjs'
68
95
 
69
- export default withAmamoMdx(amamo)({ reactStrictMode: true })
96
+ export default withAmamoMdx(amamo)({
97
+ reactStrictMode: true,
98
+ })
70
99
  ```
71
100
 
72
101
  ### Direct compiler API
73
102
 
74
- ```ts
103
+ ```js
104
+ // build-content.mjs
75
105
  import { createCompiler } from '@amamo/mdx'
76
106
 
77
107
  import amamo from './amamo.config.mjs'
78
108
 
79
109
  const compiler = await createCompiler(amamo)
110
+
80
111
  try {
81
112
  const result = await compiler.build()
82
113
  console.log(result)
@@ -85,25 +116,57 @@ try {
85
116
  }
86
117
  ```
87
118
 
88
- The first build writes these compiler-owned files under `generatedDirectory` (default
89
- `.amamo-mdx`):
119
+ Run the script with `node build-content.mjs`.
90
120
 
91
- - `collections.mjs` collection metadata with lazy imports of the source MDX files.
92
- - `collections.d.ts` — a companion declaration output for the collection registry.
93
- - `index.json` — the private index used by the Next loader.
121
+ ## Use compiled content
94
122
 
95
- Cache and manifest paths are configured separately and are resolved from `root`.
123
+ With the Vite plugin or Next wrapper configured, import an MDX file like an application module:
96
124
 
97
- ## Security boundary
125
+ ```tsx
126
+ import Post, { frontmatter } from './content/posts/hello.mdx'
98
127
 
99
- MDX modules can execute JavaScript when the host imports or renders them. Compile only content from
100
- trusted authors; schema validation is not a sandbox. Mark top-level frontmatter fields as
101
- `sensitive` to keep their plaintext out of compiled modules, cache records, and manifests. See the
102
- [security model](https://jikkai.github.io/mdx/security/) before handling secrets.
128
+ export function Page() {
129
+ return (
130
+ <main>
131
+ <h1>{frontmatter.title}</h1>
132
+ <Post />
133
+ </main>
134
+ )
135
+ }
136
+ ```
103
137
 
104
- ## Documentation
138
+ Or load a document from the generated registry:
139
+
140
+ ```ts
141
+ import { collections } from './.amamo-mdx/collections.mjs'
142
+
143
+ const hello = collections.posts.find((document) => document.slug === 'hello')
144
+ const module = await hello?.load()
145
+ ```
146
+
147
+ Registry `load()` functions import the source MDX file, so they must run through the configured Vite
148
+ plugin or Next loader.
105
149
 
106
- The complete English and Simplified Chinese documentation covers:
150
+ ## Generated files
151
+
152
+ The first build writes these files under `generatedDirectory`, which defaults to `.amamo-mdx`:
153
+
154
+ - `collections.mjs` — sorted collection metadata and lazy source imports;
155
+ - `collections.d.ts` — TypeScript declarations for the registry;
156
+ - `index.json` — the source-to-cache index used by the Next loader.
157
+
158
+ Cache and manifest paths are configured separately from `generatedDirectory`. Add `.amamo-mdx/` to
159
+ the host repository's ignore file unless the application deliberately tracks generated output.
160
+
161
+ ## Package entry points
162
+
163
+ | Import | Use it for |
164
+ | ----------------- | ------------------------------------------ |
165
+ | `@amamo/mdx` | Configuration and the direct compiler API. |
166
+ | `@amamo/mdx/vite` | Vite 8 development and production builds. |
167
+ | `@amamo/mdx/next` | Next 16 development and production builds. |
168
+
169
+ ## Documentation
107
170
 
108
171
  - [Getting started](https://jikkai.github.io/mdx/getting-started/)
109
172
  - [Configuration reference](https://jikkai.github.io/mdx/configuration/)
package/dist/config.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import * as z from 'zod';
2
+ export { z };
1
3
  export type JsonValue = null | boolean | number | string | JsonValue[] | {
2
4
  [key: string]: JsonValue;
3
5
  };
@@ -13,20 +15,32 @@ export interface ILocaleConfig {
13
15
  export interface ISlugConfig {
14
16
  indexNames?: string[];
15
17
  }
18
+ export interface IFrontmatterSchema {
19
+ readonly shape: Readonly<Record<string, unknown>>;
20
+ toJSONSchema(): unknown;
21
+ }
16
22
  export interface ICollectionConfig {
17
23
  directory: string;
18
24
  extensions?: string[];
19
25
  locales?: ILocaleConfig;
20
- schema: {
21
- [key: string]: JsonValue;
22
- };
23
- sensitive?: string[];
26
+ schema: IFrontmatterSchema;
24
27
  slug?: ISlugConfig;
25
28
  }
29
+ export interface IMathConfig {
30
+ macros?: Record<string, string>;
31
+ singleDollar?: boolean;
32
+ }
33
+ export interface IMdxExtensionsConfig {
34
+ footnotes?: boolean;
35
+ headingIds?: boolean;
36
+ taskLists?: boolean;
37
+ }
26
38
  export interface IMdxConfig {
39
+ extensions?: IMdxExtensionsConfig;
27
40
  gfm?: boolean;
28
41
  hardBreaks?: boolean;
29
42
  jsxImportSource?: string;
43
+ math?: false | IMathConfig;
30
44
  providerImportSource?: string;
31
45
  }
32
46
  export interface IHighlightConfig {
@@ -79,7 +93,6 @@ export interface INormalizedCollectionConfig {
79
93
  schema: {
80
94
  [key: string]: JsonValue;
81
95
  };
82
- sensitive: string[];
83
96
  slug: {
84
97
  indexNames: string[];
85
98
  };
@@ -105,7 +118,7 @@ export interface INormalizedManifestConfig extends Omit<IManifestConfig, 'collec
105
118
  collections: string[];
106
119
  output: string;
107
120
  }
108
- export interface INormalizedConfig {
121
+ export interface IAmamoMDXConfig {
109
122
  cache: {
110
123
  directory: string;
111
124
  enabled: boolean;
@@ -115,9 +128,20 @@ export interface INormalizedConfig {
115
128
  generatedDirectory: string;
116
129
  highlight: INormalizedHighlightConfig;
117
130
  manifests: Record<string, INormalizedManifestConfig>;
118
- mdx: Required<IMdxConfig>;
131
+ mdx: {
132
+ extensions: Required<IMdxExtensionsConfig>;
133
+ gfm: boolean;
134
+ hardBreaks: boolean;
135
+ jsxImportSource: string;
136
+ math: {
137
+ enabled: boolean;
138
+ macros: Record<string, string>;
139
+ singleDollar: boolean;
140
+ };
141
+ providerImportSource: string;
142
+ };
119
143
  media: INormalizedMediaConfig;
120
144
  root: string;
121
145
  }
122
146
  export declare function defineConfig<T extends IAmamoMdxConfig>(config: T): T;
123
- export declare function normalizeConfig(config: IAmamoMdxConfig): INormalizedConfig;
147
+ export declare function normalizeConfig(config: IAmamoMdxConfig): IAmamoMDXConfig;
package/dist/config.js CHANGED
@@ -1,5 +1,8 @@
1
+ import { Buffer } from 'node:buffer';
1
2
  import { existsSync, realpathSync } from 'node:fs';
2
3
  import path from 'node:path';
4
+ import * as z from 'zod';
5
+ export { z };
3
6
  const DEFAULT_MEDIA_ATTRIBUTES = {
4
7
  audio: ['src'],
5
8
  embed: ['src'],
@@ -9,10 +12,15 @@ const DEFAULT_MEDIA_ATTRIBUTES = {
9
12
  track: ['src'],
10
13
  video: ['src', 'poster'],
11
14
  };
15
+ const MATH_MACRO_NAME = /^\\(?:[A-Za-z@]+|[^\s])$/u;
16
+ const MAX_MATH_MACRO_BYTES = 1024;
17
+ const MAX_MATH_MACROS_BYTES = 16 * 1024;
12
18
  function notSerializable(location, reason) {
13
19
  throw new TypeError(`AMAMO_CONFIG_NOT_SERIALIZABLE: ${location} ${reason}`);
14
20
  }
15
- function assertPlainData(value, location, active) {
21
+ function assertPlainData(value, location, active, segments = []) {
22
+ if (segments.length === 3 && segments[0] === 'collections' && segments[2] === 'schema')
23
+ return;
16
24
  if (value === null || typeof value === 'string' || typeof value === 'boolean')
17
25
  return;
18
26
  if (typeof value === 'number') {
@@ -35,7 +43,7 @@ function assertPlainData(value, location, active) {
35
43
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
36
44
  if (!descriptor || !('value' in descriptor))
37
45
  notSerializable(`${location}.${key}`, 'contains an accessor');
38
- assertPlainData(descriptor.value, `${location}.${key}`, active);
46
+ assertPlainData(descriptor.value, `${location}.${key}`, active, [...segments, key]);
39
47
  }
40
48
  active.delete(value);
41
49
  }
@@ -53,14 +61,32 @@ function normalizeCollection(root, name, config) {
53
61
  throw new TypeError(`AMAMO_CONFIG_INVALID: collections.${name}.locales.default must be listed in names`);
54
62
  }
55
63
  const directory = path.resolve(root, requireNonEmpty(config.directory, `collections.${name}.directory`));
64
+ if (typeof config.schema?.shape !== 'object' ||
65
+ config.schema.shape === null ||
66
+ typeof config.schema.toJSONSchema !== 'function') {
67
+ throw new TypeError(`AMAMO_CONFIG_INVALID: collections.${name}.schema must be a compatible object schema`);
68
+ }
69
+ let schema;
70
+ try {
71
+ schema = JSON.parse(JSON.stringify(config.schema.toJSONSchema()));
72
+ }
73
+ catch (error) {
74
+ const reason = error instanceof Error ? error.message : 'could not be converted to JSON Schema';
75
+ throw new TypeError(`AMAMO_CONFIG_INVALID: collections.${name}.schema ${reason}`, {
76
+ cause: error,
77
+ });
78
+ }
79
+ if (schema.type !== 'object') {
80
+ throw new TypeError(`AMAMO_CONFIG_INVALID: collections.${name}.schema must convert to an object schema`);
81
+ }
82
+ assertPlainData(schema, `collections.${name}.schema`, new WeakSet());
56
83
  return {
57
84
  directory: existsSync(directory) ? realpathSync.native(directory) : directory,
58
85
  extensions: [...extensions],
59
86
  locales: config.locales
60
87
  ? { default: config.locales.default, names: [...config.locales.names] }
61
88
  : undefined,
62
- schema: config.schema,
63
- sensitive: [...(config.sensitive ?? [])],
89
+ schema,
64
90
  slug: { indexNames: [...(config.slug?.indexNames ?? ['index', 'page'])] },
65
91
  };
66
92
  }
@@ -69,6 +95,9 @@ export function defineConfig(config) {
69
95
  }
70
96
  export function normalizeConfig(config) {
71
97
  assertPlainData(config, 'config', new WeakSet());
98
+ if (Object.hasOwn(config, 'math')) {
99
+ throw new TypeError('AMAMO_CONFIG_INVALID: math moved to mdx.math');
100
+ }
72
101
  if (!config.collections || Object.keys(config.collections).length === 0) {
73
102
  throw new TypeError('AMAMO_CONFIG_INVALID: collections must not be empty');
74
103
  }
@@ -89,7 +118,26 @@ export function normalizeConfig(config) {
89
118
  },
90
119
  ]));
91
120
  const highlight = config.highlight === false ? undefined : config.highlight;
121
+ const math = config.mdx?.math === false ? undefined : config.mdx?.math;
92
122
  const media = config.media === false ? undefined : config.media;
123
+ const gfm = config.mdx?.gfm ?? true;
124
+ let macroBytes = 0;
125
+ for (const [name, expansion] of Object.entries(math?.macros ?? {})) {
126
+ if (!name.startsWith('\\')) {
127
+ throw new TypeError(`AMAMO_CONFIG_INVALID: mdx.math.macros.${name} must start with a backslash`);
128
+ }
129
+ if (!MATH_MACRO_NAME.test(name)) {
130
+ throw new TypeError(`AMAMO_CONFIG_INVALID: mdx.math.macros.${name} must be one TeX control sequence`);
131
+ }
132
+ const expansionBytes = Buffer.byteLength(expansion);
133
+ if (expansionBytes > MAX_MATH_MACRO_BYTES) {
134
+ throw new TypeError(`AMAMO_CONFIG_INVALID: mdx.math.macros.${name} exceeds the ${MAX_MATH_MACRO_BYTES}-byte limit`);
135
+ }
136
+ macroBytes += Buffer.byteLength(name) + expansionBytes;
137
+ if (macroBytes > MAX_MATH_MACROS_BYTES) {
138
+ throw new TypeError(`AMAMO_CONFIG_INVALID: mdx.math.macros exceeds the ${MAX_MATH_MACROS_BYTES}-byte total limit`);
139
+ }
140
+ }
93
141
  return {
94
142
  cache: {
95
143
  directory: path.resolve(root, config.cache === false
@@ -116,9 +164,19 @@ export function normalizeConfig(config) {
116
164
  },
117
165
  manifests,
118
166
  mdx: {
119
- gfm: config.mdx?.gfm ?? true,
167
+ extensions: {
168
+ footnotes: config.mdx?.extensions?.footnotes ?? gfm,
169
+ headingIds: config.mdx?.extensions?.headingIds ?? false,
170
+ taskLists: config.mdx?.extensions?.taskLists ?? gfm,
171
+ },
172
+ gfm,
120
173
  hardBreaks: config.mdx?.hardBreaks ?? false,
121
174
  jsxImportSource: config.mdx?.jsxImportSource ?? 'react',
175
+ math: {
176
+ enabled: config.mdx?.math !== undefined && config.mdx.math !== false,
177
+ macros: { ...math?.macros },
178
+ singleDollar: math?.singleDollar ?? true,
179
+ },
122
180
  providerImportSource: config.mdx?.providerImportSource ?? '',
123
181
  },
124
182
  media: {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { IAmamoMdxConfig, ICacheConfig, ICollectionConfig, IDerivedConfig, IHighlightConfig, ILocaleConfig, IManifestConfig, IMdxConfig, IMediaConfig, INormalizedConfig, JsonValue, ManifestField, } from './config.js';
2
- export { defineConfig, normalizeConfig } from './config.js';
1
+ export type { IAmamoMDXConfig, IAmamoMdxConfig, ICacheConfig, ICollectionConfig, IDerivedConfig, IFrontmatterSchema, IHighlightConfig, ILocaleConfig, IManifestConfig, IMathConfig, IMdxConfig, IMdxExtensionsConfig, IMediaConfig, JsonValue, ManifestField, } from './config.js';
2
+ export { defineConfig, normalizeConfig, z } from './config.js';
3
3
  export type { IBuildResult, ICompiler, ITransformResult } from './compiler.js';
4
4
  export { createCompiler } from './compiler.js';
5
5
  export type { IDiagnostic } from './native.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { defineConfig, normalizeConfig } from './config.js';
1
+ export { defineConfig, normalizeConfig, z } from './config.js';
2
2
  export { createCompiler } from './compiler.js';
package/dist/native.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { INormalizedConfig, JsonValue } from './config.js';
1
+ import type { IAmamoMDXConfig, JsonValue } from './config.js';
2
2
  export interface ISourcePoint {
3
3
  column: number;
4
4
  line: number;
@@ -65,7 +65,7 @@ export declare class AmamoMdxError extends Error {
65
65
  readonly diagnostics: IDiagnostic[];
66
66
  constructor(diagnostics: IDiagnostic[]);
67
67
  }
68
- export declare function configurationFingerprint(config: INormalizedConfig): string;
69
- export declare function prepareNativeBatch(config: INormalizedConfig, inputs: INativeDocumentInput[]): IPreparedNativeBatch;
68
+ export declare function configurationFingerprint(config: IAmamoMDXConfig): string;
69
+ export declare function prepareNativeBatch(config: IAmamoMDXConfig, inputs: INativeDocumentInput[]): IPreparedNativeBatch;
70
70
  export declare function pruneNativeCache(cacheDirectory: string, keepKeys: string[]): number;
71
- export declare function renderNativeManifests(config: INormalizedConfig, records: IDocumentRecord[]): IRenderedManifest[];
71
+ export declare function renderNativeManifests(config: IAmamoMDXConfig, records: IDocumentRecord[]): IRenderedManifest[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amamo/mdx",
3
- "version": "0.1.6",
3
+ "version": "0.3.0",
4
4
  "description": "A native MDX content compiler with Vite and Next adapters",
5
5
  "homepage": "https://jikkai.github.io/mdx/",
6
6
  "license": "MIT",
@@ -48,24 +48,27 @@
48
48
  "test": "pnpm run build && vitest run src/__tests__",
49
49
  "test:rust": "cargo test",
50
50
  "typecheck": "tsc -p tsconfig.json --noEmit",
51
- "check": "pnpm run format:check && pnpm run lint && cargo clippy --all-targets -- -D warnings && cargo test && pnpm run typecheck && pnpm run test && pnpm run check:docs",
51
+ "check": "pnpm run format:check && pnpm run lint && pnpm run check:rust && pnpm run check:npm && pnpm run check:docs",
52
52
  "check:docs": "pnpm --filter @amamo/mdx-docs types:check && pnpm --filter @amamo/mdx-docs build",
53
+ "check:npm": "pnpm run typecheck && pnpm run test",
54
+ "check:rust": "cargo clippy --all-targets -- -D warnings && pnpm run test:rust",
53
55
  "release": "verso"
54
56
  },
55
57
  "dependencies": {
56
- "shiki": "4.4.2"
58
+ "shiki": "4.4.3",
59
+ "zod": "4.4.3"
57
60
  },
58
61
  "devDependencies": {
59
- "@amamo/oxlint-config": "1.0.0",
60
- "@amamo/verso": "1.0.1",
61
- "@napi-rs/cli": "3.8.2",
62
- "@types/node": "26.1.2",
62
+ "@amamo/oxlint-config": "1.1.0",
63
+ "@amamo/verso": "1.1.0",
64
+ "@napi-rs/cli": "3.8.6",
65
+ "@types/node": "26.2.0",
63
66
  "@types/react": "19.2.18",
64
67
  "@types/react-dom": "19.2.4",
65
68
  "lint-staged": "17.3.0",
66
- "next": "16.3.0",
67
- "oxfmt": "0.62.0",
68
- "oxlint": "1.77.0",
69
+ "next": "16.3.1",
70
+ "oxfmt": "0.63.0",
71
+ "oxlint": "1.78.0",
69
72
  "react": "19.2.8",
70
73
  "react-dom": "19.2.8",
71
74
  "simple-git-hooks": "2.13.1",
@@ -116,12 +119,12 @@
116
119
  },
117
120
  "packageManager": "pnpm@11.20.0",
118
121
  "optionalDependencies": {
119
- "@amamo/mdx-darwin-arm64": "0.1.6",
120
- "@amamo/mdx-darwin-x64": "0.1.6",
121
- "@amamo/mdx-linux-arm64-gnu": "0.1.6",
122
- "@amamo/mdx-linux-x64-gnu": "0.1.6",
123
- "@amamo/mdx-linux-arm64-musl": "0.1.6",
124
- "@amamo/mdx-linux-x64-musl": "0.1.6",
125
- "@amamo/mdx-win32-x64-msvc": "0.1.6"
122
+ "@amamo/mdx-darwin-arm64": "0.3.0",
123
+ "@amamo/mdx-darwin-x64": "0.3.0",
124
+ "@amamo/mdx-linux-arm64-gnu": "0.3.0",
125
+ "@amamo/mdx-linux-x64-gnu": "0.3.0",
126
+ "@amamo/mdx-linux-arm64-musl": "0.3.0",
127
+ "@amamo/mdx-linux-x64-musl": "0.3.0",
128
+ "@amamo/mdx-win32-x64-msvc": "0.3.0"
126
129
  }
127
130
  }