Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
37 changes: 37 additions & 0 deletions .github/workflows/heap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Heap memory

on:
pull_request:
push:
branches:
- main

permissions: {}

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
heap:
name: Heap regression check
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 24.x

- name: Install
run: npm ci

- name: Build
run: npm run build

- name: Run heap check
run: scripts/heap-check
6 changes: 4 additions & 2 deletions .jscpd.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
{
"threshold": 5,
"reporters": ["console"],
"reporters": [
"console"
],
"ignore": [
"NOTICE.txt",
"node_modules/**",
"dist/**",
"build/**",
"src/es/apis/schemas/**",
"src/es/apis/**",
"src/cloud/apis/**",
"packages/es-schemas/src/**",
"test/**",
"codegen/**",
"specs/**",
Expand Down
2 changes: 1 addition & 1 deletion .mega-linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ TYPESCRIPT_ES_CONFIG_FILE: eslint.config.js
FILTER_REGEX_EXCLUDE: "(node_modules/|dist/|build/|\\.git/)"

# Skip generated code for ESLint (39k lines of schemas + 2k lines of cloud APIs)
TYPESCRIPT_ES_FILTER_REGEX_EXCLUDE: "(src/es/apis/schemas/|src/cloud/apis/|packages/es-schemas/src/)"
TYPESCRIPT_ES_FILTER_REGEX_EXCLUDE: "(src/es/apis/schemas/|src/cloud/apis/)"

# yamllint: skip test fixtures (custom multi-doc YAML DSL)
YAML_YAMLLINT_CONFIG_FILE: .yamllint.yml
Expand Down
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Architecture Decisions

Read this before proposing structural changes. Each section documents a deliberate choice made for startup performance or memory efficiency, validated by `scripts/perf-check`. Do not refactor these unless benchmarks prove the change is neutral.
Read this before proposing structural changes. Each section documents a deliberate choice made for startup performance or memory efficiency, validated by `scripts/perf-check` (startup latency) and `scripts/heap-check` (heap allocation). Do not refactor these unless benchmarks prove the change is neutral.

## Factory Core Split (`factory-core.ts` / `factory.ts`)

Expand Down
382 changes: 379 additions & 3 deletions NOTICE.txt

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion codegen/functional/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from './types.ts'
import { buildActionMap, mapAction } from './mapper.ts'
import type { MappedAction } from './mapper.ts'
import type { SchemaArgDefinition } from '../../src/lib/schema-args.ts'
import type { SchemaArgDefinition } from '../../src/lib/json-schema-args.ts'

export interface GenerateResult {
/** bash script content */
Expand Down
35 changes: 4 additions & 31 deletions codegen/functional/mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,23 @@
*/

import type { EsApiDefinition } from '../../src/es/types.ts'
import { resolveInput } from '../../src/es/types.ts'
import { extractSchemaArgs } from '../../src/lib/schema-args.ts'
import type { SchemaArgDefinition } from '../../src/lib/schema-args.ts'
import { extractSchemaArgs } from '../../src/lib/json-schema-args.ts'
import type { SchemaArgDefinition } from '../../src/lib/json-schema-args.ts'

/**
* Result of mapping a YAML dot-notation action to a CLI command.
* Contains the CLI arguments needed to invoke the command.
*/
export interface MappedAction {
/** CLI args: ['es', namespace?, name, ...flags] */
cliArgs: string[]
/** true if the action accepts a request body */
hasBody: boolean
/** schema args lookup by key, for resolving body field flags */
/** schema args lookup by key */
bodyArgsByKey: Map<string, SchemaArgDefinition>
/** set of body field keys */
bodyFields: Set<string>
}

/**
* Builds a lookup from YAML dot-notation action names to EsApiDefinitions.
*
* YAML uses `namespace.name` (e.g. "indices.create") or just `name` (e.g. "get").
* Definitions with `namespace` are keyed as `namespace.name`.
* Definitions without `namespace` are keyed as just `name`.
*/
export function buildActionMap (definitions: EsApiDefinition[]): Map<string, EsApiDefinition> {
const map = new Map<string, EsApiDefinition>()
for (const def of definitions) {
Expand All @@ -39,22 +30,11 @@ export function buildActionMap (definitions: EsApiDefinition[]): Map<string, EsA
return map
}

/**
* Maps a YAML test action to CLI arguments.
*
* @param action - dot-notation action name (e.g. "indices.create", "get")
* @param params - YAML action parameters (path + query params, excluding body)
* @param actionMap - lookup map from buildActionMap
* @returns MappedAction with CLI args, or null if the action isn't registered
*/
export function mapAction (
action: string,
params: Record<string, unknown>,
actionMap: Map<string, EsApiDefinition>
): MappedAction | null {
// YAML tests use underscore notation (e.g. "clear_scroll", "cat.ml_data_frame_analytics")
// but CLI definitions use kebab-case (e.g. "clear-scroll", "cat.ml-data-frame-analytics").
// Normalize by converting underscores to hyphens within each dot-separated segment.
const normalizedAction = action.split('.').map((s) => s.replace(/_/g, '-')).join('.')
const def = actionMap.get(action) ?? actionMap.get(normalizedAction)
if (def == null) return null
Expand All @@ -63,9 +43,7 @@ export function mapAction (
if (def.namespace != null) args.push(def.namespace)
args.push(def.name)

const schemaArgs = def.input != null
? extractSchemaArgs(resolveInput(def.input))
: []
const schemaArgs = def.input != null ? extractSchemaArgs(def.input) : []

const bodyFields = new Set(
schemaArgs.filter((a) => a.foundIn === 'body').map((a) => a.schemaKey)
Expand All @@ -78,13 +56,8 @@ export function mapAction (

for (const [key, value] of Object.entries(params)) {
if (key === 'ignore') continue

const argDef = argsByKey.get(key)
// Skip params the CLI doesn't expose as flags (e.g. cat's 'format')
if (argDef == null) continue

// Body fields from YAML params are passed as CLI flags (same as non-body params);
// they will be handled alongside any explicit body in buildCommand.
args.push(`--${argDef.cliFlag}`, String(value))
}

Expand Down
26 changes: 6 additions & 20 deletions codegen/functional/test/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { z } from 'zod'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { parseTestFile } from '../parser.ts'
Expand All @@ -21,56 +20,43 @@ const testDefs: EsApiDefinition[] = [
description: 'Create an index',
method: 'PUT',
path: '/{index}',
input: z.object({
index: z.string().meta({ found_in: 'path' })
})
input: { type: 'object', properties: { index: { type: 'string', 'x-found-in': 'path' } }, required: ['index'] }
},
{
name: 'delete',
namespace: 'indices',
description: 'Delete an index',
method: 'DELETE',
path: '/{index}',
input: z.object({
index: z.string().meta({ found_in: 'path' })
})
input: { type: 'object', properties: { index: { type: 'string', 'x-found-in': 'path' } }, required: ['index'] }
},
{
name: 'get',
description: 'Get a document',
method: 'GET',
path: '/{index}/_doc/{id}',
input: z.object({
id: z.string().meta({ found_in: 'path' }),
index: z.string().meta({ found_in: 'path' })
})
input: { type: 'object', properties: { id: { type: 'string', 'x-found-in': 'path' }, index: { type: 'string', 'x-found-in': 'path' } }, required: ['id', 'index'] }
},
{
name: 'index',
description: 'Index a document',
method: 'POST',
path: '/{index}/_doc',
input: z.object({
index: z.string().meta({ found_in: 'path' })
})
input: { type: 'object', properties: { index: { type: 'string', 'x-found-in': 'path' } }, required: ['index'] }
},
{
name: 'count',
description: 'Count documents',
method: 'GET',
path: '/{index}/_count',
input: z.object({
index: z.string().meta({ found_in: 'path' })
})
input: { type: 'object', properties: { index: { type: 'string', 'x-found-in': 'path' } }, required: ['index'] }
},
{
name: 'bulk',
description: 'Bulk operations',
method: 'POST',
path: '/_bulk',
input: z.object({
refresh: z.boolean().optional().meta({ found_in: 'query' })
})
input: { type: 'object', properties: { refresh: { type: 'boolean', 'x-found-in': 'query' } } }
}
]

Expand Down
29 changes: 18 additions & 11 deletions codegen/functional/test/mapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { z } from 'zod'
import { buildActionMap, mapAction } from '../mapper.ts'
import type { EsApiDefinition } from '../../../src/es/types.ts'

Expand All @@ -16,22 +15,30 @@ const testDefs: EsApiDefinition[] = [
description: 'Create an index',
method: 'PUT',
path: '/{index}',
input: z.object({
index: z.string().meta({ found_in: 'path' }),
wait_for_active_shards: z.string().optional().meta({ found_in: 'query' }),
settings: z.record(z.string(), z.unknown()).optional().meta({ found_in: 'body' })
})
input: {
type: 'object',
properties: {
index: { type: 'string', 'x-found-in': 'path' },
wait_for_active_shards: { type: 'string', 'x-found-in': 'query' },
settings: { type: 'object', 'x-found-in': 'body' },
},
required: ['index'],
}
},
{
name: 'get',
description: 'Get a document',
method: 'GET',
path: '/{index}/_doc/{id}',
input: z.object({
id: z.string().meta({ found_in: 'path' }),
index: z.string().meta({ found_in: 'path' }),
refresh: z.boolean().optional().meta({ found_in: 'query' })
})
input: {
type: 'object',
properties: {
id: { type: 'string', 'x-found-in': 'path' },
index: { type: 'string', 'x-found-in': 'path' },
refresh: { type: 'boolean', 'x-found-in': 'query' },
},
required: ['id', 'index'],
}
},
{
name: 'info',
Expand Down
Loading
Loading