nextjs-middleware-stack 1.1.0 โ 2.0.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 +148 -88
- package/dist/main.d.ts +27 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +27 -7
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,155 +1,186 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ๐ฃ `middlewareStack` โ A Composable Middleware Router for Next.js
|
|
2
2
|
|
|
3
|
-
A composable middleware stack for Next.js
|
|
3
|
+
A lightweight, composable middleware stack for Next.js that supports:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- `string` route patterns (with dynamic parameters & wildcards)
|
|
6
|
+
- Raw `RegExp`
|
|
7
|
+
- Sync/async predicate functions
|
|
8
|
+
- Strict `pipe()` composition API
|
|
9
|
+
|
|
10
|
+
Perfect for authentication gates, logging, redirects, feature flags, i18n integration, and layered request logic in Next.js Edge or Node runtimes.
|
|
6
11
|
|
|
7
12
|
---
|
|
8
13
|
|
|
9
14
|
## โจ Features
|
|
10
15
|
|
|
11
|
-
- โ
|
|
16
|
+
- โ
Clean `pipe()` API (no tuple arrays)
|
|
17
|
+
- โ
String pattern matching via `compare-path`
|
|
18
|
+
|
|
12
19
|
- `:param` dynamic segments
|
|
13
|
-
- `**` wildcards
|
|
14
|
-
|
|
15
|
-
- โ
|
|
16
|
-
- โ
|
|
17
|
-
- โ
|
|
20
|
+
- `**` greedy wildcards
|
|
21
|
+
|
|
22
|
+
- โ
Native `RegExp` support
|
|
23
|
+
- โ
Sync or async predicate matching
|
|
24
|
+
- โ
Middleware short-circuiting
|
|
25
|
+
- โ
Edge & Node compatible
|
|
26
|
+
- โ
Type-safe enforcement of `pipe()` usage
|
|
18
27
|
|
|
19
28
|
---
|
|
20
29
|
|
|
21
30
|
## ๐ฆ Installation
|
|
22
31
|
|
|
23
32
|
```bash
|
|
24
|
-
npm install
|
|
33
|
+
npm install nextjs-middleware-stack
|
|
25
34
|
```
|
|
26
35
|
|
|
27
36
|
---
|
|
28
37
|
|
|
29
38
|
## ๐ง API
|
|
30
39
|
|
|
31
|
-
### `
|
|
40
|
+
### `pipe(pattern, handler)`
|
|
32
41
|
|
|
33
|
-
Creates a
|
|
42
|
+
Creates a middleware pipe.
|
|
34
43
|
|
|
35
44
|
```ts
|
|
36
45
|
export type PatternFn = (req: Request) => boolean | Promise<boolean>
|
|
37
46
|
export type Pattern = PatternFn | string | RegExp
|
|
38
47
|
```
|
|
39
48
|
|
|
40
|
-
#### Parameters
|
|
49
|
+
#### Parameters
|
|
50
|
+
|
|
51
|
+
- `pattern`
|
|
41
52
|
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
- a `RegExp`
|
|
46
|
-
- a function/async function `(req) => boolean | Promise<boolean>`
|
|
47
|
-
- The second item is a handler function `(req: NextRequest) => Response | void | Promise<Response | void>`.
|
|
53
|
+
- string route shape (`/users/:id`)
|
|
54
|
+
- `RegExp`
|
|
55
|
+
- predicate function `(req) => boolean | Promise<boolean>`
|
|
48
56
|
|
|
49
|
-
|
|
57
|
+
- `handler`
|
|
50
58
|
|
|
51
|
-
-
|
|
59
|
+
- `(req: Request) => Response | void | Promise<Response | void>`
|
|
60
|
+
|
|
61
|
+
> `pipe()` must be used. Raw `[pattern, handler]` tuples are not supported.
|
|
52
62
|
|
|
53
63
|
---
|
|
54
64
|
|
|
55
|
-
|
|
65
|
+
### `middlewareStack(pipes)`
|
|
56
66
|
|
|
57
|
-
|
|
67
|
+
```ts
|
|
68
|
+
middlewareStack(pipes: Pipe[])
|
|
69
|
+
```
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
| --------------- | --------------------------- |
|
|
61
|
-
| `/user/:id` | `/user/42` |
|
|
62
|
-
| `/users/[id]` | `/users/42` (same as above) |
|
|
63
|
-
| `/docs/**/edit` | `/docs/api/v1/intro/edit` |
|
|
64
|
-
| `/a/:x/**/b/:y` | `/a/1/foo/bar/b/2` |
|
|
71
|
+
Creates a Next.js-compatible middleware handler from an ordered list of `pipe()` calls.
|
|
65
72
|
|
|
66
|
-
|
|
73
|
+
Returns:
|
|
67
74
|
|
|
68
|
-
|
|
75
|
+
```ts
|
|
76
|
+
;(req: Request) => Promise<Response | void>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Ready to export as default from `middleware.ts`.
|
|
69
80
|
|
|
70
81
|
---
|
|
71
82
|
|
|
72
|
-
##
|
|
83
|
+
## ๐ Example (Authentication Middleware)
|
|
73
84
|
|
|
74
85
|
```ts
|
|
75
|
-
//
|
|
86
|
+
// apps/example-app/src/middleware.ts
|
|
76
87
|
import { NextResponse } from 'next/server'
|
|
77
|
-
import { middlewareStack } from '
|
|
88
|
+
import { middlewareStack, pipe } from 'nextjs-middleware-stack'
|
|
78
89
|
import { validateAuthToken } from './utils/validate-jwt'
|
|
79
90
|
|
|
80
91
|
export default middlewareStack([
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
pipe(/^\/dashboard\/.*/, async (req) => {
|
|
93
|
+
const token = req.cookies.get('AUTH_TOKEN')?.value
|
|
94
|
+
const isValid = token && (await validateAuthToken(token))
|
|
95
|
+
|
|
96
|
+
if (!isValid) {
|
|
97
|
+
return NextResponse.redirect(new URL('/login', req.url))
|
|
98
|
+
}
|
|
99
|
+
}),
|
|
100
|
+
|
|
101
|
+
pipe('/public/:page', async () => {
|
|
102
|
+
console.log('Public page hit')
|
|
103
|
+
}),
|
|
104
|
+
|
|
105
|
+
// Always run last
|
|
106
|
+
pipe(
|
|
107
|
+
() => true,
|
|
108
|
+
async () => {
|
|
109
|
+
console.log('Final middleware')
|
|
110
|
+
}
|
|
111
|
+
),
|
|
95
112
|
])
|
|
96
113
|
```
|
|
97
114
|
|
|
98
115
|
---
|
|
99
116
|
|
|
100
|
-
##
|
|
117
|
+
## ๐ Supported Path Shapes
|
|
118
|
+
|
|
119
|
+
String patterns are powered by `compare-path`.
|
|
120
|
+
|
|
121
|
+
| Pattern | Matches |
|
|
122
|
+
| --------------- | ------------------------- |
|
|
123
|
+
| `/user/:id` | `/user/42` |
|
|
124
|
+
| `/users/[id]` | `/users/42` |
|
|
125
|
+
| `/docs/**/edit` | `/docs/api/v1/intro/edit` |
|
|
126
|
+
| `/a/:x/**/b/:y` | `/a/1/foo/bar/b/2` |
|
|
127
|
+
|
|
128
|
+
Use `RegExp` for advanced matching.
|
|
129
|
+
|
|
130
|
+
Use a predicate function when matching depends on:
|
|
131
|
+
|
|
132
|
+
- Cookies
|
|
133
|
+
- Headers
|
|
134
|
+
- Geo
|
|
135
|
+
- Runtime conditions
|
|
136
|
+
- Feature flags
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## ๐งช Example: String Matching
|
|
101
141
|
|
|
102
142
|
```ts
|
|
103
|
-
|
|
104
|
-
import { middlewareStack } from './utils/middleware-stack'
|
|
143
|
+
import { middlewareStack, pipe } from 'nextjs-middleware-stack'
|
|
105
144
|
|
|
106
145
|
export default middlewareStack([
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
],
|
|
119
|
-
[
|
|
120
|
-
'hello/**',
|
|
121
|
-
async (req) => {
|
|
122
|
-
// Match anything starting with /hello/
|
|
123
|
-
},
|
|
124
|
-
],
|
|
146
|
+
pipe('/users/:id', async (req) => {
|
|
147
|
+
// Handle specific user route
|
|
148
|
+
}),
|
|
149
|
+
|
|
150
|
+
pipe('cars/:id', async () => {
|
|
151
|
+
// Match /cars/123
|
|
152
|
+
}),
|
|
153
|
+
|
|
154
|
+
pipe('hello/**', async () => {
|
|
155
|
+
// Match anything under /hello/
|
|
156
|
+
}),
|
|
125
157
|
])
|
|
126
158
|
```
|
|
127
159
|
|
|
128
160
|
---
|
|
129
161
|
|
|
130
|
-
## ๐งช Example
|
|
162
|
+
## ๐งช Example: Predicate Function
|
|
131
163
|
|
|
132
164
|
```ts
|
|
133
|
-
import { middlewareStack } from '
|
|
165
|
+
import { middlewareStack, pipe } from 'nextjs-middleware-stack'
|
|
134
166
|
|
|
135
167
|
export default middlewareStack([
|
|
136
|
-
|
|
168
|
+
pipe(
|
|
137
169
|
async (req) => {
|
|
138
|
-
// Match only API requests that include an auth header
|
|
139
170
|
return req.url.includes('/api/') && !!req.headers.get('authorization')
|
|
140
171
|
},
|
|
141
|
-
async (
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
172
|
+
async () => {
|
|
173
|
+
console.log('Authenticated API request')
|
|
174
|
+
}
|
|
175
|
+
),
|
|
145
176
|
])
|
|
146
177
|
```
|
|
147
178
|
|
|
148
179
|
---
|
|
149
180
|
|
|
150
|
-
## ๐งน
|
|
181
|
+
## ๐งน Next.js Integration
|
|
151
182
|
|
|
152
|
-
You must also export
|
|
183
|
+
You must also export `config` to control where middleware runs:
|
|
153
184
|
|
|
154
185
|
```ts
|
|
155
186
|
export const config = {
|
|
@@ -157,7 +188,6 @@ export const config = {
|
|
|
157
188
|
'/((?!_next|.*\\..*).*)', // Skip static assets
|
|
158
189
|
'/(api|trpc)(.*)', // Always include API routes
|
|
159
190
|
],
|
|
160
|
-
runtime: 'nodejs',
|
|
161
191
|
}
|
|
162
192
|
```
|
|
163
193
|
|
|
@@ -165,20 +195,50 @@ export const config = {
|
|
|
165
195
|
|
|
166
196
|
## ๐ง How It Works
|
|
167
197
|
|
|
168
|
-
|
|
198
|
+
For each request:
|
|
199
|
+
|
|
200
|
+
1. Iterate through pipes in order.
|
|
201
|
+
2. Evaluate the `pattern`.
|
|
202
|
+
3. If matched โ execute the `handler`.
|
|
203
|
+
4. If the handler returns a `Response`, execution stops.
|
|
204
|
+
5. Otherwise โ continue to the next pipe.
|
|
169
205
|
|
|
170
|
-
|
|
171
|
-
2. If matched, execute the handler.
|
|
172
|
-
3. If a `Response` is returned from the handler, it stops further execution and returns immediately.
|
|
206
|
+
Middleware order matters.
|
|
173
207
|
|
|
174
208
|
---
|
|
175
209
|
|
|
176
210
|
## ๐ก Tips
|
|
177
211
|
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
|
|
181
|
-
|
|
212
|
+
- Place authentication gates before public routes.
|
|
213
|
+
- Put always-run middleware last:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
pipe(() => true, someMiddleware)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
- Prefer string shapes for readability.
|
|
220
|
+
- Use predicate functions for request-aware logic.
|
|
221
|
+
- Use `RegExp` only when necessary.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## โ ๏ธ Breaking Change (v2+)
|
|
226
|
+
|
|
227
|
+
`middlewareStack` now accepts **only `pipe()` entries**.
|
|
228
|
+
|
|
229
|
+
Old style:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
middlewareStack([[/regex/, handler]])
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
is no longer supported.
|
|
236
|
+
|
|
237
|
+
Migration:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
middlewareStack([pipe(/regex/, handler)])
|
|
241
|
+
```
|
|
182
242
|
|
|
183
243
|
---
|
|
184
244
|
|
package/dist/main.d.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
/** A middleware handler that can short-circuit by returning a Response. */
|
|
2
|
+
export type MiddlewareHandler<T = Request> = (req: T) => Response | void | Promise<Response | void>;
|
|
3
|
+
/** A pattern function can be sync or async. */
|
|
2
4
|
export type PatternFn = (req: Request) => boolean | Promise<boolean>;
|
|
3
5
|
export type Pattern = PatternFn | string | RegExp;
|
|
4
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Opaque/Branded "Pipe" type. Enforces that consumers MUST use pipe() (raw tuples won't
|
|
8
|
+
* type-check).
|
|
9
|
+
*/
|
|
10
|
+
declare const PIPE_BRAND: unique symbol;
|
|
11
|
+
export type Pipe<T extends Request = Request> = Readonly<{
|
|
12
|
+
/** Brand */
|
|
13
|
+
[PIPE_BRAND]: true;
|
|
14
|
+
pattern: Pattern;
|
|
15
|
+
handler: MiddlewareHandler<T>;
|
|
16
|
+
}>;
|
|
17
|
+
/** Create a middleware pipe (pattern + handler). */
|
|
18
|
+
export declare function pipe<T extends Request = Request>(pattern: Pattern, handler: MiddlewareHandler<T>): Pipe<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Create a middleware stack that executes pipes in order.
|
|
21
|
+
*
|
|
22
|
+
* - For each pipe: if pattern matches, run handler.
|
|
23
|
+
* - If handler returns a Response, short-circuit and return it.
|
|
24
|
+
* - Otherwise continue to the next pipe.
|
|
25
|
+
*
|
|
26
|
+
* NOTE: This version enforces "pipe() only" (no raw [pattern, handler] tuples).
|
|
27
|
+
*/
|
|
28
|
+
export declare function middlewareStack<T extends Request = Request>(pipes: ReadonlyArray<Pipe<T>>): (req: T) => Promise<Response | void>;
|
|
29
|
+
export {};
|
|
5
30
|
//# sourceMappingURL=main.d.ts.map
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAEA,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;AAEnG,+CAA+C;AAC/C,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;AACpE,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAA;AAEjD;;;GAGG;AACH,QAAA,MAAM,UAAU,EAAE,OAAO,MAA6B,CAAA;AAEtD,MAAM,MAAM,IAAI,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,IAAI,QAAQ,CAAC;IACvD,YAAY;IACZ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAA;CAC9B,CAAC,CAAA;AAEF,oDAAoD;AACpD,wBAAgB,IAAI,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAC9C,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC5B,IAAI,CAAC,CAAC,CAAC,CAET;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAC1E,KAAK,CAAC,KAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAuBhD"}
|
package/dist/main.js
CHANGED
|
@@ -9,24 +9,44 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.pipe = pipe;
|
|
12
13
|
exports.middlewareStack = middlewareStack;
|
|
13
14
|
const compare_path_1 = require("compare-path");
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Opaque/Branded "Pipe" type. Enforces that consumers MUST use pipe() (raw tuples won't
|
|
17
|
+
* type-check).
|
|
18
|
+
*/
|
|
19
|
+
const PIPE_BRAND = Symbol('PIPE_BRAND');
|
|
20
|
+
/** Create a middleware pipe (pattern + handler). */
|
|
21
|
+
function pipe(pattern, handler) {
|
|
22
|
+
return { pattern, handler, [PIPE_BRAND]: true };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create a middleware stack that executes pipes in order.
|
|
26
|
+
*
|
|
27
|
+
* - For each pipe: if pattern matches, run handler.
|
|
28
|
+
* - If handler returns a Response, short-circuit and return it.
|
|
29
|
+
* - Otherwise continue to the next pipe.
|
|
30
|
+
*
|
|
31
|
+
* NOTE: This version enforces "pipe() only" (no raw [pattern, handler] tuples).
|
|
32
|
+
*/
|
|
33
|
+
function middlewareStack(pipes) {
|
|
15
34
|
return (req) => __awaiter(this, void 0, void 0, function* () {
|
|
16
35
|
var _a, _b;
|
|
17
|
-
|
|
18
|
-
|
|
36
|
+
for (const { pattern, handler } of pipes) {
|
|
37
|
+
// Recompute each iteration: a previous handler may mutate req.nextUrl.pathname
|
|
38
|
+
const pathname = (_b = (_a = req.nextUrl) === null || _a === void 0 ? void 0 : _a.pathname) !== null && _b !== void 0 ? _b : new URL(req.url).pathname;
|
|
19
39
|
const isMatch = typeof pattern === 'string'
|
|
20
|
-
? (0, compare_path_1.comparePath)(pattern,
|
|
40
|
+
? (0, compare_path_1.comparePath)(pattern, pathname)[0]
|
|
21
41
|
: pattern instanceof RegExp
|
|
22
|
-
? pattern.test(
|
|
42
|
+
? pattern.test(pathname)
|
|
23
43
|
: typeof pattern === 'function'
|
|
24
44
|
? yield pattern(req)
|
|
25
45
|
: false;
|
|
26
46
|
if (isMatch) {
|
|
27
47
|
const result = yield handler(req);
|
|
28
|
-
if (result)
|
|
29
|
-
return result;
|
|
48
|
+
if (result !== undefined)
|
|
49
|
+
return result;
|
|
30
50
|
}
|
|
31
51
|
}
|
|
32
52
|
});
|
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;;;;;;;;;;AAuBA,oBAKC;AAWD,0CAwBC;AA/DD,+CAA0C;AAS1C;;;GAGG;AACH,MAAM,UAAU,GAAkB,MAAM,CAAC,YAAY,CAAC,CAAA;AAStD,oDAAoD;AACpD,SAAgB,IAAI,CAClB,OAAgB,EAChB,OAA6B;IAE7B,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,UAAU,CAAC,EAAE,IAAI,EAAa,CAAA;AAC5D,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,eAAe,CAA8B,KAA6B;IACxF,OAAO,CAAO,GAAM,EAA4B,EAAE;;QAGhD,KAAK,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,KAAK,EAAE,CAAC;YACzC,+EAA+E;YAC/E,MAAM,QAAQ,GACZ,MAAA,MAAC,GAAwB,CAAC,OAAO,0CAAE,QAAQ,mCAAI,IAAI,GAAG,CAAE,GAAe,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;YAEvF,MAAM,OAAO,GACX,OAAO,OAAO,KAAK,QAAQ;gBACzB,CAAC,CAAC,IAAA,0BAAW,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACnC,CAAC,CAAC,OAAO,YAAY,MAAM;oBAC3B,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;oBACxB,CAAC,CAAC,OAAO,OAAO,KAAK,UAAU;wBAC/B,CAAC,CAAC,MAAM,OAAO,CAAC,GAAyB,CAAC;wBAC1C,CAAC,CAAC,KAAK,CAAA;YAEX,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAA;gBACjC,IAAI,MAAM,KAAK,SAAS;oBAAE,OAAO,MAAM,CAAA;YACzC,CAAC;QACH,CAAC;IACH,CAAC,CAAA,CAAA;AACH,CAAC"}
|