@bakery-framework/core 2.0.0-alpha.9 → 2.0.0-rc.2
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 +8 -8
- package/package.json +2 -5
- package/src/cache/lru.ts +2 -2
- package/src/cache/shared-db.ts +81 -11
- package/src/cache/string.ts +4 -0
- package/src/cache/tiered.ts +11 -5
- package/src/client/globals.d.ts +7 -7
- package/src/client/livereload.ts +2 -2
- package/src/client/utils.ts +11 -6
- package/src/compiler/compiler.ts +48 -44
- package/src/compiler/dev-service.ts +29 -30
- package/src/compiler/prompt-tracker.ts +2 -2
- package/src/compiler/tsconfig-sync.ts +130 -59
- package/src/core/bakery.ts +6 -3
- package/src/core/cache-version.ts +68 -18
- package/src/core/config.ts +16 -16
- package/src/core/context.ts +54 -8
- package/src/core/define-route.ts +5 -6
- package/src/core/index.ts +29 -8
- package/src/core/init.ts +38 -35
- package/src/core/jsx.ts +19 -4
- package/src/core/paths.ts +3 -1
- package/src/core/plugins.ts +15 -4
- package/src/core/port.ts +6 -6
- package/src/global.d.ts +36 -18
- package/src/handlers/assets/google-font.ts +6 -6
- package/src/handlers/assets/image.ts +36 -3
- package/src/handlers/assets/nm.ts +7 -7
- package/src/handlers/assets/static.ts +55 -12
- package/src/handlers/assets/ts.ts +3 -3
- package/src/handlers/assets/tsx.ts +1 -1
- package/src/handlers/assets/virtual-asset.ts +1 -1
- package/src/handlers/core/$base.ts +24 -8
- package/src/handlers/core/$dynamic.ts +66 -23
- package/src/handlers/core/$error.ts +29 -17
- package/src/handlers/core/$middleware.ts +6 -6
- package/src/handlers/core/$mounts.ts +2 -2
- package/src/handlers/core/$registry.ts +16 -6
- package/src/handlers/core/$routing.ts +18 -19
- package/src/handlers/core/$static.ts +11 -12
- package/src/handlers/routes/api.ts +5 -5
- package/src/handlers/routes/html.ts +2 -2
- package/src/handlers/routes/proxy.ts +1 -1
- package/src/logger/clients.ts +2 -2
- package/src/logger/logger.ts +4 -4
- package/src/logger/serve-log.ts +32 -18
- package/src/plugins/index.ts +1 -1
- package/src/plugins/routes.ts +7 -7
- package/src/plugins/types.ts +13 -11
- package/src/router.ts +12 -12
- package/src/session.ts +41 -23
- package/src/shared.d.ts +5 -5
- package/src/startup.ts +3 -3
- package/src/types.d.ts +10 -10
- package/src/utils/common/case.ts +1 -1
- package/src/utils/common/match.ts +1 -1
- package/src/utils/common/misc.ts +1 -1
- package/src/utils/constants.ts +15 -15
- package/src/utils/fs.ts +97 -39
- package/src/utils/http/authorize.ts +24 -17
- package/src/utils/http/cors.ts +6 -6
- package/src/utils/http/credential.ts +4 -4
- package/src/utils/http/csrf.ts +6 -7
- package/src/utils/http/dom.ts +9 -9
- package/src/utils/http/etag.ts +54 -13
- package/src/utils/http/html.ts +29 -20
- package/src/utils/http/ip.ts +1 -1
- package/src/utils/http/sse.ts +11 -11
- package/src/utils/http/url.ts +4 -4
- package/src/utils/http/validate.ts +3 -4
- package/src/utils/isomorphic/escape.ts +1 -1
- package/src/utils/isomorphic/is.ts +2 -2
- package/src/utils/isomorphic/misc.ts +1 -1
- package/src/utils/isomorphic/stringify.ts +2 -3
- package/src/utils/isomorphic/try.ts +1 -1
- package/src/utils/shared-pool.ts +23 -16
- package/tsconfig.app.json +3 -3
- package/tsconfig.vue.json +3 -3
package/src/utils/http/sse.ts
CHANGED
|
@@ -3,25 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Bakery had WebSockets and nothing for one-way streaming, which is the cheaper
|
|
5
5
|
* half of that pair and the right shape for progress, notifications and tailing
|
|
6
|
-
|
|
6
|
+
*: no upgrade, no protocol, reconnects handled by the browser.
|
|
7
7
|
*
|
|
8
8
|
* A route could always return a `Response` wrapping a `ReadableStream` and it
|
|
9
9
|
* would reach the client intact: `ETag.sendResponse` returns early without an
|
|
10
10
|
* `ETag` header, and `injectIfHtml` ignores anything that is not HTML. What was
|
|
11
|
-
* missing is the framing, and framing is where this goes wrong
|
|
11
|
+
* missing is the framing, and framing is where this goes wrong: a payload
|
|
12
12
|
* containing a newline silently truncates unless every line is prefixed, and a
|
|
13
13
|
* write after the client has gone throws where nobody is catching.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/** One event. Every field is optional except the payload. */
|
|
17
17
|
export interface SSEMessage {
|
|
18
|
-
/**
|
|
18
|
+
/** Serialized with `JSON.stringify` unless it is already a string. */
|
|
19
19
|
data: unknown
|
|
20
|
-
/** `event
|
|
20
|
+
/** `event:`: the client listens for this name instead of `message`. */
|
|
21
21
|
event?: string
|
|
22
|
-
/** `id
|
|
22
|
+
/** `id:`: echoed back as `Last-Event-ID` when the browser reconnects. */
|
|
23
23
|
id?: string
|
|
24
|
-
/** `retry
|
|
24
|
+
/** `retry:`: how long the browser waits before reconnecting, in ms. */
|
|
25
25
|
retry?: number
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -42,7 +42,7 @@ export interface SSEOptions {
|
|
|
42
42
|
* Milliseconds between automatic keep-alive comments. `0` disables them.
|
|
43
43
|
*
|
|
44
44
|
* Defaults to 15s because idle proxies and load balancers commonly cut a
|
|
45
|
-
* connection at 30
|
|
45
|
+
* connection at 30 to 60s, and a dead SSE stream is invisible: the browser
|
|
46
46
|
* reconnects silently, so the symptom is duplicated work on the server rather
|
|
47
47
|
* than an error anyone sees.
|
|
48
48
|
*/
|
|
@@ -55,7 +55,7 @@ export interface SSEOptions {
|
|
|
55
55
|
* Encode one message as an SSE frame.
|
|
56
56
|
*
|
|
57
57
|
* **Every line of `data` is prefixed.** A payload containing a newline is
|
|
58
|
-
* otherwise cut short at that newline
|
|
58
|
+
* otherwise cut short at that newline: the rest is read as a new field, and
|
|
59
59
|
* the client sees a truncated message rather than an error. Pretty-printed JSON
|
|
60
60
|
* and stack traces both hit this.
|
|
61
61
|
*/
|
|
@@ -72,7 +72,7 @@ export function encodeSSE(message: SSEMessage): string {
|
|
|
72
72
|
: JSON.stringify(message.data)
|
|
73
73
|
|
|
74
74
|
// `?? ''` rather than skipping: `JSON.stringify(undefined)` is undefined, and
|
|
75
|
-
// a frame with no `data:` line at all is a comment to the client
|
|
75
|
+
// a frame with no `data:` line at all is a comment to the client. The event
|
|
76
76
|
// would vanish rather than arrive empty.
|
|
77
77
|
for (const line of (payload ?? '').split('\n')) lines.push(`data: ${line}`)
|
|
78
78
|
|
|
@@ -116,7 +116,7 @@ export function sse(
|
|
|
116
116
|
controller.enqueue(encoder.encode(chunk))
|
|
117
117
|
} catch {
|
|
118
118
|
// The client went away between the `closed` check and the enqueue. That
|
|
119
|
-
// is a normal race on every disconnect, not an error worth surfacing
|
|
119
|
+
// is a normal race on every disconnect, not an error worth surfacing,
|
|
120
120
|
// and throwing here would reject inside a timer callback, where nothing
|
|
121
121
|
// is catching.
|
|
122
122
|
finish()
|
|
@@ -192,7 +192,7 @@ export function sse(
|
|
|
192
192
|
'Cache-Control': 'no-cache, no-transform',
|
|
193
193
|
Connection: 'keep-alive',
|
|
194
194
|
// nginx buffers proxied responses by default, which holds every event
|
|
195
|
-
// until the buffer fills
|
|
195
|
+
// until the buffer fills: the stream appears to work in development and
|
|
196
196
|
// to hang in production. This is the documented opt-out.
|
|
197
197
|
'X-Accel-Buffering': 'no',
|
|
198
198
|
},
|
package/src/utils/http/url.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The request's parsed URL, parsed at most once.
|
|
3
3
|
*
|
|
4
4
|
* Why memoize at all: `new URL` measures ~1.7µs, and the router, the body
|
|
5
|
-
* parser and the proxy handler all want the same parse of the same request
|
|
5
|
+
* parser and the proxy handler all want the same parse of the same request,
|
|
6
6
|
* so the parse was hoisted into the server's `fetch` in `packages/cli/src/worker.ts`
|
|
7
7
|
* and shared.
|
|
8
8
|
*
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
* `(req as any).__parsedUrl`: two writers, six readers across three packages,
|
|
11
11
|
* every reader spelled `(req as any).__parsedUrl || new URL(req.url)` so that
|
|
12
12
|
* it silently re-parsed whenever the writer had not run first. That is a
|
|
13
|
-
* writer/reader ordering hazard with no way to observe it going wrong
|
|
13
|
+
* writer/reader ordering hazard with no way to observe it going wrong: a
|
|
14
14
|
* missed write costs microseconds, not correctness, so nothing ever fails.
|
|
15
15
|
* One function collapses both roles: there is no ordering left to get wrong,
|
|
16
16
|
* no `any` cast anywhere, and no writer to forget. The map also keeps the
|
|
17
|
-
* framework from mutating a `Request` object it does not own
|
|
18
|
-
* `Request` goes in and comes back unchanged
|
|
17
|
+
* framework from mutating a `Request` object it does not own (a caller's
|
|
18
|
+
* `Request` goes in and comes back unchanged), and holds its keys weakly, so
|
|
19
19
|
* an entry dies with the request rather than needing eviction (convention 6).
|
|
20
20
|
*
|
|
21
21
|
* There is deliberately no setter. Nothing outside this module needs to say
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Request-body validation for route modules.
|
|
3
3
|
*
|
|
4
|
-
* `defineRoute<{ title: string }>` declares a body shape and enforces nothing
|
|
5
|
-
* the scaffolder's own template says so twice: *"declares the contract — it
|
|
4
|
+
* `defineRoute<{ title: string }>` declares a body shape and enforces nothing ( * the scaffolder's own template says so twice: *"declares the contract) it
|
|
6
5
|
* does not validate it. The body is still client input."* Every route was left
|
|
7
6
|
* to hand-check or not, and most did not.
|
|
8
7
|
*
|
|
@@ -10,7 +9,7 @@
|
|
|
10
9
|
* core` has zero runtime dependencies and that is worth keeping, so validation
|
|
11
10
|
* accepts two shapes it can consume without knowing who produced them:
|
|
12
11
|
*
|
|
13
|
-
* - **Standard Schema** (`~standard`)
|
|
12
|
+
* - **Standard Schema** (`~standard`): the shared interface zod, valibot and
|
|
14
13
|
* arktype all implement. Bring your own library; Bakery never imports it.
|
|
15
14
|
* - **A plain function** that returns the parsed value or throws.
|
|
16
15
|
*
|
|
@@ -59,7 +58,7 @@ function isStandardSchema<T>(v: Validator<T>): v is StandardSchemaLike<T> {
|
|
|
59
58
|
/**
|
|
60
59
|
* Render a Standard Schema path as dotted notation.
|
|
61
60
|
*
|
|
62
|
-
* Segments may be plain keys or `{ key }` objects
|
|
61
|
+
* Segments may be plain keys or `{ key }` objects: the spec allows both, and a
|
|
63
62
|
* library that uses the object form would otherwise render as `[object Object]`
|
|
64
63
|
* in the very message meant to tell someone which field is wrong.
|
|
65
64
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* These live in `isomorphic/` because both sides need them: the HTML pipeline
|
|
5
5
|
* and the Vue plugin escape on the way out, and the client bundle escapes when
|
|
6
6
|
* building DOM strings. Nothing here may reference `Bun.*`, node builtins, or
|
|
7
|
-
* DOM globals
|
|
7
|
+
* DOM globals: this module is compiled into the browser bundle as-is.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const HTML_ESCAPES: Record<string, string> = {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Two implementations of this used to exist. This one keeps the *callable*
|
|
5
5
|
* shape from the client copy (`is(x, 'string')` as well as `is.string(x)`,
|
|
6
6
|
* which is what the global `ISFunction` type has always described) and the
|
|
7
|
-
* *semantics* of the server copy
|
|
7
|
+
* *semantics* of the server copy, notably `is.object([]) === true`, which
|
|
8
8
|
* `misc.test.ts` asserts and which callers like `router.ts` rely on to
|
|
9
9
|
* JSON-encode array response bodies.
|
|
10
10
|
*/
|
|
@@ -16,7 +16,7 @@ const checks = {
|
|
|
16
16
|
bigint: (v: any): v is bigint => typeof v === 'bigint',
|
|
17
17
|
symbol: (v: any): v is symbol => typeof v === 'symbol',
|
|
18
18
|
function: (v: any): v is Function => typeof v === 'function',
|
|
19
|
-
/** Arrays count as objects here
|
|
19
|
+
/** Arrays count as objects here. See the note above before changing this. */
|
|
20
20
|
object: (v: any): v is Record<string, any> =>
|
|
21
21
|
v !== null && typeof v === 'object',
|
|
22
22
|
array: Array.isArray,
|
|
@@ -20,7 +20,7 @@ export function any<T = any>(value: any): T {
|
|
|
20
20
|
*
|
|
21
21
|
* Isomorphic, and moving it here is what made it so: it lived in
|
|
22
22
|
* `client/utils.ts` as a browser global only, so the same call in a server
|
|
23
|
-
* block was a ReferenceError
|
|
23
|
+
* block was a ReferenceError, reported from an app. `crypto` is a global in
|
|
24
24
|
* both runtimes; nothing here is browser-specific.
|
|
25
25
|
*/
|
|
26
26
|
export function randomId(length = 8) {
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Circular-safe JSON helpers.
|
|
3
3
|
*
|
|
4
4
|
* Three copies of this logic existed: the client `$fmt` formatter, the
|
|
5
|
-
* livereload log serializer, and ad-hoc replacers on the server. They drifted
|
|
6
|
-
*
|
|
7
|
-
* render as `""` — so the kernel lives here once and callers layer their own
|
|
5
|
+
* livereload log serializer, and ad-hoc replacers on the server. They drifted ( * the `$fmt` one shipped with a one-parameter replacer that made every object
|
|
6
|
+
* render as `""`), so the kernel lives here once and callers layer their own
|
|
8
7
|
* extras on top.
|
|
9
8
|
*/
|
|
10
9
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MixedPromise, Wrapped } from '../../types'
|
|
2
2
|
import { is } from './is'
|
|
3
3
|
|
|
4
|
-
// These were declared again, here, byte-for-byte identical to `types.d.ts
|
|
4
|
+
// These were declared again, here, byte-for-byte identical to `types.d.ts`:
|
|
5
5
|
// the second copy convention 5 exists to prevent. `isomorphic.test.ts` catches
|
|
6
6
|
// a duplicated *value* by reference identity, and a type has no runtime
|
|
7
7
|
// identity to compare, so nothing saw it.
|
package/src/utils/shared-pool.ts
CHANGED
|
@@ -22,11 +22,29 @@ export class SharedMemoryPool {
|
|
|
22
22
|
header!: Int32Array
|
|
23
23
|
counters!: Int32Array
|
|
24
24
|
rateLimits!: Int32Array
|
|
25
|
-
dataPool!: Uint8Array
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* **The default is the layout's own size, not a megabyte.**
|
|
28
|
+
*
|
|
29
|
+
* It was `1024 * 1024`, and the layout uses 9,280 bytes of it: a 64-byte
|
|
30
|
+
* header, 1 KB of counters and 8 KB of rate-limit slots. The remaining
|
|
31
|
+
* 1,039,296 bytes were a `dataPool` region that **nothing read** - grep it
|
|
32
|
+
* across every package and app, and the only reference outside this file was
|
|
33
|
+
* a test asserting it was a `Uint8Array`. So 99.1% of a `SharedArrayBuffer`
|
|
34
|
+
* allocated at import in every server process existed for one assertion.
|
|
35
|
+
*
|
|
36
|
+
* The region is gone rather than shrunk. A scratch area with no consumer is
|
|
37
|
+
* not a feature waiting to be used; it is a number nobody can size, because
|
|
38
|
+
* there is nothing to size it against. Something that needs shared scratch
|
|
39
|
+
* later adds it along with the code that reads it.
|
|
40
|
+
*
|
|
41
|
+
* A larger size is still accepted and still honored, because `bind()` reads
|
|
42
|
+
* the size out of the header rather than trusting `byteLength` - so a
|
|
43
|
+
* cluster master that allocates more and shares it still works.
|
|
44
|
+
*/
|
|
45
|
+
constructor(sizeOrBuffer: number | SharedArrayBuffer = BUFFER_START_OFFSET) {
|
|
28
46
|
if (typeof sizeOrBuffer === 'number') {
|
|
29
|
-
const size = Math.max(sizeOrBuffer, BUFFER_START_OFFSET
|
|
47
|
+
const size = Math.max(sizeOrBuffer, BUFFER_START_OFFSET)
|
|
30
48
|
this.buffer = new SharedArrayBuffer(size)
|
|
31
49
|
this.header = new Int32Array(this.buffer, 0, HEADER_INT_COUNT)
|
|
32
50
|
this.counters = new Int32Array(
|
|
@@ -39,17 +57,11 @@ export class SharedMemoryPool {
|
|
|
39
57
|
HEADER_BYTES + COUNTERS_BYTES,
|
|
40
58
|
RATE_LIMIT_SLOT_COUNT * 2,
|
|
41
59
|
)
|
|
42
|
-
this.dataPool = new Uint8Array(
|
|
43
|
-
this.buffer,
|
|
44
|
-
BUFFER_START_OFFSET,
|
|
45
|
-
size - BUFFER_START_OFFSET,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
60
|
Atomics.store(this.header, 0, 0x42414b45)
|
|
49
61
|
Atomics.store(this.header, 1, size)
|
|
50
62
|
Atomics.store(this.header, 2, BUFFER_START_OFFSET)
|
|
51
63
|
} else {
|
|
52
|
-
// Adopting a buffer someone else laid out is exactly what `bind` does
|
|
64
|
+
// Adopting a buffer someone else laid out is exactly what `bind` does:
|
|
53
65
|
// it read the header for the size rather than trusting `byteLength`, and
|
|
54
66
|
// so did the copy that used to sit here, character for character.
|
|
55
67
|
this.bind(sizeOrBuffer)
|
|
@@ -70,12 +82,7 @@ export class SharedMemoryPool {
|
|
|
70
82
|
HEADER_BYTES + COUNTERS_BYTES,
|
|
71
83
|
RATE_LIMIT_SLOT_COUNT * 2,
|
|
72
84
|
)
|
|
73
|
-
|
|
74
|
-
this.dataPool = new Uint8Array(
|
|
75
|
-
this.buffer,
|
|
76
|
-
BUFFER_START_OFFSET,
|
|
77
|
-
size - BUFFER_START_OFFSET,
|
|
78
|
-
)
|
|
85
|
+
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
incrementCounter(slot: number, delta = 1): number {
|
package/tsconfig.app.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": [
|
|
3
|
-
"**Client-side code
|
|
3
|
+
"**Client-side code**, the plain `.ts` under your source root that ends up",
|
|
4
4
|
"in the browser bundle. Extend it:",
|
|
5
5
|
" { \"extends\": \"@bakery-framework/core/tsconfig.app.json\" }",
|
|
6
6
|
"",
|
|
7
7
|
"BREAKING, 2.0.0: this file used to be the single config every app",
|
|
8
8
|
"extended, and it carried `bun-types` and the JSX settings. It is now the",
|
|
9
|
-
"*browser* half of a three-way split
|
|
9
|
+
"*browser* half of a three-way split: server code extends",
|
|
10
10
|
"tsconfig.server.json, Vue SFCs extend tsconfig.vue.json.",
|
|
11
11
|
"",
|
|
12
12
|
"**`types` is deliberately empty.** That is the entire reason the split",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"tsconfig.server.json; a `.tsx` file compiled under this config would pick up",
|
|
19
19
|
"the automatic runtime and fail at request time while typechecking clean."
|
|
20
20
|
],
|
|
21
|
-
"_files_note": "client/globals.d.ts declares the globals the browser runtime binds onto globalThis (request, escapeHTML, is, Try, Case, match, …). It states each as `typeof import(...)` of the isomorphic module that implements it, so the declaration cannot drift from the value. Those modules are pure by convention
|
|
21
|
+
"_files_note": "client/globals.d.ts declares the globals the browser runtime binds onto globalThis (request, escapeHTML, is, Try, Case, match, …). It states each as `typeof import(...)` of the isomorphic module that implements it, so the declaration cannot drift from the value. Those modules are pure by convention (no Bun, no node builtins, no DOM), which is what makes them safe to pull into a project that has no bun-types.",
|
|
22
22
|
"files": [
|
|
23
23
|
"./src/client/globals.d.ts",
|
|
24
24
|
"./src/shared.d.ts",
|
package/tsconfig.vue.json
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
" { \"extends\": \"@bakery-framework/core/tsconfig.vue.json\" }",
|
|
5
5
|
"",
|
|
6
6
|
"SFCs get `bun-types` like server code does, because @bakery-framework/",
|
|
7
|
-
"plugin-vue compiles and can render them on the server
|
|
7
|
+
"plugin-vue compiles and can render them on the server, a `<script>` block",
|
|
8
8
|
"is not browser-only the way a plain `.ts` under the source root is.",
|
|
9
9
|
"",
|
|
10
10
|
"**`tsc` cannot parse `.vue`.** This config is for `vue-tsc`, which wraps tsc",
|
|
11
11
|
"and understands SFCs. Pointing plain `tsc` at a project whose `include`",
|
|
12
12
|
"matches `**/*.vue` gets you either silence or a parse error, depending on",
|
|
13
|
-
"version
|
|
13
|
+
"version, neither of which is a typecheck.",
|
|
14
14
|
"",
|
|
15
15
|
"`jsx: preserve` rather than Bakery's classic runtime: inside an SFC, JSX (if",
|
|
16
16
|
"used at all) belongs to Vue's own transform, not to `createElement`."
|
|
17
17
|
],
|
|
18
|
-
"_files_note": "Server ambients, same as tsconfig.server.json
|
|
18
|
+
"_files_note": "Server ambients, same as tsconfig.server.json. An SFC can reach Bakery, AppConfig and the JSX namespace.",
|
|
19
19
|
"files": ["./src/global.d.ts", "./src/shared.d.ts", "./src/types.d.ts"],
|
|
20
20
|
"compilerOptions": {
|
|
21
21
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|