Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions apps/mcp/src/mcp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ test('MCP server handles generate_data_from_spec tool call', () => {
expect(response?.result?.structuredContent?.ok).toBe(true);
});

test('MCP server allows registered domain commands in safe mode', () => {
const response = requestServer({
jsonrpc: '2.0',
id: 202,
method: 'tools/call',
params: {
name: 'generate_data_from_spec',
arguments: {
textSpec: 't2\nautoIncrement.timestamp(start="12th June 2026 at 4pm", step=60, type="minutes")',
rowCount: 3,
outputFormat: 'json',
},
},
});
const payload = JSON.parse(response?.result?.content?.[0]?.text || '{}');
expect(payload.ok).toBe(true);
expect(payload.rows).toEqual([['2026-06-12T16:00:00Z'], ['2026-06-12T17:00:00Z'], ['2026-06-12T18:00:00Z']]);
expect(response?.result?.isError).toBe(false);
});

test('MCP server accepts comments and blank lines in textSpec', () => {
const response = requestServer({
jsonrpc: '2.0',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
slug: release-prep-combinatorial-grid-workflows
title: "Release Prep: Stronger Combinatorial Generation and Faster Grid Workflows"
title: 'Release Prep: Stronger Combinatorial Generation and Faster Grid Workflows'
authors: [alan]
tags: [release, feature, combinatorial, schema, import, export, ux]
date: 2026-06-12T10:00
Expand All @@ -12,7 +12,30 @@ This release adds broader combinatorial generation, schema authoring improvement

<!-- truncate -->

## 1. N-wise combinatorial generation, not just pairwise
## 1. Auto-increment timestamps for deterministic event streams

You can now generate timestamps that move forward one row at a time instead of relying on purely random dates.

Example:

```text
CreatedAt
autoIncrement.timestamp(start="2026-06-12T12:39:23Z", step=1, type="seconds")
```

That produces:

- row 1: `2026-06-12T12:39:23Z`
- row 2: `2026-06-12T12:39:24Z`
- row 3: `2026-06-12T12:39:25Z`

This is useful for audit logs, event streams, ordered API records, and any test data where time should progress predictably across generated rows.

Docs:

- [autoIncrement Domain](/docs/test-data/domain/autoIncrement)

## 2. N-wise combinatorial generation, not just pairwise

The biggest addition is that combinatorial generation now goes beyond pairwise.

Expand Down Expand Up @@ -42,7 +65,7 @@ Docs:

![N-wise combinations dialog](/img/release-198/n-wise-generation.png)

## 2. Schema constraints with PICT-style `IF ... THEN ...`
## 3. Schema constraints with PICT-style `IF ... THEN ...`

Schema constraints make generated combinations more realistic by filtering out invalid rows.

Expand Down Expand Up @@ -71,7 +94,7 @@ Docs:

- [Schema Definition](/docs/test-data/Schema-Definition)

## 3. Grid to Enum Schema for turning existing tables into generators
## 4. Grid to Enum Schema for turning existing tables into generators

If you already have representative data in the main grid, you can now turn that grid into an enum schema automatically.

Expand Down Expand Up @@ -103,7 +126,7 @@ Docs:

![Grid to enum schema in the app](/img/release-198/grid-to-enum-schema.png)

## 4. Constraint-aware auto-increment sequences for generated identifiers
## 5. Constraint-aware auto-increment sequences for generated identifiers

Schemas can now generate sequential IDs through the domain model with `autoIncrement.sequence`.

Expand All @@ -128,7 +151,7 @@ Docs:

- [Auto Increment Sequences](/docs/test-data/auto-increment-sequences)

## 5. PICT-style inline enum definitions such as `Name: values`
## 6. PICT-style inline enum definitions such as `Name: values`

Schema text now fits more naturally with compact PICT-style authoring.

Expand All @@ -153,7 +176,7 @@ Docs:

- [Schema Definition](/docs/test-data/Schema-Definition)

## 6. Import trimming controls for cleaner amend and import workflows
## 7. Import trimming controls for cleaner amend and import workflows

Imported files and clipboard data can now be normalized during import.

Expand All @@ -176,7 +199,7 @@ Docs:

![Import trim settings](/img/release-198/import-trim-settings.png)

## 7. File export settings for line endings and BOM
## 8. File export settings for line endings and BOM

Downloads now support file transport settings without changing the preview text shown in the browser.

Expand All @@ -193,7 +216,7 @@ Docs:

![Download encoding settings](/img/release-198/export-encoding-settings.png)

## 8. Right-click context menu in the main data grid
## 9. Right-click context menu in the main data grid

The editable grid now has a right-click context menu for common grid actions.

Expand All @@ -203,7 +226,7 @@ Docs:

- [Data Grid Editable](/docs/test-data/data-grid-editable)

## 9. Always-visible total row counts in the data grid
## 10. Always-visible total row counts in the data grid

The main grid now shows total row counts, and filtered views also show how many rows remain visible.

Expand Down
7 changes: 6 additions & 1 deletion docs-src/docs/040-test-data/018-Schema-Definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ Customer Name
person.fullName
```

This generates names such as `Alice Smith`.
```text
CreatedAt
autoIncrement.timestamp(start="2026-06-12T12:39:23Z", step=1, type="seconds")
```

The `Customer Name` example generates names such as `Alice Smith`. The `CreatedAt` example generates deterministic timestamps for time-ordered rows.

## Comments and blank lines

Expand Down
9 changes: 7 additions & 2 deletions docs-src/docs/040-test-data/domain/000-domain-test-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ Date
date.between(from=1577836800000, to=1659312000000)
```

```txt
CreatedAt
autoIncrement.timestamp(start="2026-06-12T12:39:23Z", step=1, type="seconds")
```

```txt
IBAN
finance.iban(formatted=true, countryCode="GB")
Expand All @@ -62,9 +67,9 @@ For faker helper templates and utility functions, use faker helpers:

## Domains

- [autoIncrement](/docs/test-data/domain/autoIncrement)
- [airline](/docs/test-data/domain/airline)
- [animal](/docs/test-data/domain/animal)
- [autoIncrement](/docs/test-data/domain/autoIncrement)
- [book](/docs/test-data/domain/book)
- [color](/docs/test-data/domain/color)
- [commerce](/docs/test-data/domain/commerce)
Expand All @@ -89,4 +94,4 @@ For faker helper templates and utility functions, use faker helpers:
- [string](/docs/test-data/domain/string)
- [system](/docs/test-data/domain/system)
- [vehicle](/docs/test-data/domain/vehicle)
- [word](/docs/test-data/domain/word)
- [word](/docs/test-data/domain/word)
39 changes: 38 additions & 1 deletion docs-src/docs/040-test-data/domain/040-autoIncrement.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: "Domain keyword reference for autoIncrement."

# autoIncrement Domain

The `autoIncrement` domain provides stateful sequence helpers for accepted generated rows.
The `autoIncrement` domain provides deterministic values that move forward for each generated row.

## Methods

Expand Down Expand Up @@ -43,3 +43,40 @@ Example return values:
- `1`
- `15`
- `filename001.txt`

### `autoIncrement.timestamp`

Generates a timestamp that starts from a fixed point and increments by the configured amount for each generated row.

- Canonical: `awd.domain.autoIncrement.timestamp`

| Arg | Type | Required | Description |
| --- | --- | --- | --- |
| `start` | `string\|number` | no | Starting timestamp. Defaults to the generation run start time. Valid examples include `2026-06-12T12:39:23Z`, `20/03/1969`, `12-06-2026 12:39:23`, or a Unix timestamp such as `1718195963000`. |
| `step` | `number` | no | Amount added for each generated row. Defaults to `1`. |
| `type` | `string` | no | Unit applied to `step` for each row. Supports `milliseconds`, `seconds`, `minutes`, `hours`, `days`, `weeks`, `months`, or `years`. Defaults to `seconds`. |
| `outputFormat` | `string` | no | Output format. Defaults to ISO-8601 without milliseconds. Use `iso8601` for the default behaviour or a custom pattern such as `yyyy-MM-dd HH:mm:ss`. |
| `inputFormat` | `string` | no | Optional parse pattern used only for `start` when you want to match a specific text shape such as `dd/MM/yyyy` or `dd-MM-yyyy HH:mm:ss`. |

Examples:

```txt
autoIncrement.timestamp()
```

```txt
autoIncrement.timestamp(start="2026-06-12T12:39:23Z", step=1, type="seconds")
```

```txt
autoIncrement.timestamp(start="20/03/1969", step=1, type="days", outputFormat="yyyy-MM-dd")
```

```txt
autoIncrement.timestamp(start="12-06-2026 12:39:23", step=15, type="minutes", outputFormat="yyyy-MM-dd HH:mm:ss", inputFormat="dd-MM-yyyy HH:mm:ss")
```

Example return values:
- `2026-06-12T12:39:23Z`
- `2026-06-12T12:39:24Z`
- `2026-06-12T12:39:25Z`
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"test:browser": "node ./node_modules/@playwright/test/cli.js test",
"test:browser:headed": "node ./node_modules/@playwright/test/cli.js test --headed",
"test:browser:install": "node ./node_modules/@playwright/test/cli.js install chromium",
"test:api": "node ./node_modules/@playwright/test/cli.js test --config=playwright-api.config.js",
"test:api": "node ./scripts/run-playwright-api-tests.mjs",
"test:api:headed": "node ./node_modules/@playwright/test/cli.js test --config=playwright-api.config.js --headed",
"test:api:verbose": "node ./node_modules/@playwright/test/cli.js test --config=playwright-api.config.js --reporter=verbose",
"lint": "eslint \"apps/**/*.js\" \"packages/**/*.js\"",
Expand Down Expand Up @@ -103,6 +103,9 @@
"@anywaydata/core": "workspace:^",
"@mcp-b/webmcp-polyfill": "3.0.0",
"@popperjs/core": "2.11.8",
"chrono-node": "^2.9.1",
"date-fns": "^4.4.0",
"date-fns-tz": "^3.2.0",
"papaparse": "5.5.3",
"randexp": "0.5.3",
"tippy.js": "6.3.7"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,18 @@ describe('Test Data Command Catalog', () => {
it('should also populate domain commands for the domain dropdown section', () => {
const domainCommands = getDomainCommands();
expect(domainCommands.length).toBeGreaterThan(0);
expect(domainCommands).toContain('autoIncrement.timestamp');
expect(domainCommands).toContain('number.int');
expect(domainCommands).toContain('string.counterString');
expect(domainCommands.some((command) => command.startsWith('helpers.'))).toBe(false);
});

it('should provide picker options with schema help metadata', () => {
const values = getMethodPickerOptions('');
const autoIncrementEntry = values.find((entry) => entry.command === 'autoIncrement.timestamp');
const domainEntry = values.find((entry) => entry.command === 'number.int');
const fakerEntry = values.find((entry) => entry.command === 'helpers.arrayElement');
expect(autoIncrementEntry?.sourceType).toBe('domain');
expect(domainEntry?.sourceType).toBe('domain');
expect(fakerEntry?.sourceType).toBe('faker');
expect(Array.isArray(domainEntry?.helpModel?.params)).toBe(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ describe('help-model-builder', () => {
expect(renderSchemaHelpHtml(model)).toContain('delimiter');
});

test('builds domain help for auto-increment timestamps with step metadata', () => {
const model = buildSchemaHelpModel('domain', 'autoIncrement.timestamp');

expect(model.show).toBe(true);
expect(model.heading).toContain('autoIncrement.timestamp');
expect(renderSchemaHelpHtml(model)).toContain('run start time');
expect(renderSchemaHelpHtml(model)).toContain('outputFormat');
});

test('preserves custom domain docs links and parameters for auto increment help', () => {
const model = buildSchemaHelpModel('domain', 'autoIncrement.sequence');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ describe('domain command help metadata docs links', () => {
expect(enumHelp.docsUrl).toBe('https://anywaydata.com/docs/test-data/domain/datatype');
});

test('maps autoIncrement timestamp help to the autoIncrement domain page', () => {
const autoIncrementHelp = getDomainCommandHelp('autoIncrement.timestamp');
expect(autoIncrementHelp).toBeTruthy();
expect(autoIncrementHelp.docsUrl).toBe('https://anywaydata.com/docs/test-data/domain/autoIncrement');
});

test('keeps explicit anywaydata docs pages for custom domain commands', () => {
const sequenceHelp = getDomainCommandHelp('autoIncrement.sequence');
expect(sequenceHelp).toBeTruthy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class DomainTestDataGenerator {
this.nextAutoIncrementRuleId = 1;
}

generateFrom(aRule) {
generateFrom(aRule, executionContext = {}) {
const ruleSpec = String(aRule?.ruleSpec || '').trim();
const parsed = parseKeywordInvocation(ruleSpec);
if (Array.isArray(parsed?.errors) && parsed.errors.length > 0) {
Expand All @@ -19,6 +19,7 @@ class DomainTestDataGenerator {

try {
const result = executeDomainKeyword(parsed.keyword, {
...executionContext,
faker: this.faker,
args: Array.isArray(parsed.args) ? parsed.args : [],
autoIncrementState: this.#getAutoIncrementStateForRule(aRule),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,15 +202,16 @@ export class PairwiseTestDataGenerator {
* for non-enum rules
*/
generateCompleteDataRecords(enumCombinations) {
return enumCombinations.map((enumRecord) => {
const runStartedAt = new Date();
return enumCombinations.map((enumRecord, rowIndex) => {
const completeRecord = {};

// Preserve original schema order across enum and non-enum columns.
for (const rule of this.orderedRules) {
if (this.isRuleType(rule, 'enum')) {
completeRecord[rule.name] = enumRecord.get(rule.name);
} else {
completeRecord[rule.name] = this.generateRandomValueForRule(rule);
completeRecord[rule.name] = this.generateRandomValueForRule(rule, { rowIndex, runStartedAt });
}
}

Expand All @@ -221,7 +222,7 @@ export class PairwiseTestDataGenerator {
/**
* Generate a random value for a non-enum rule
*/
generateRandomValueForRule(rule) {
generateRandomValueForRule(rule, generationContext = {}) {
switch (rule.type) {
case 'literal':
return rule.ruleSpec;
Expand All @@ -236,7 +237,7 @@ export class PairwiseTestDataGenerator {
return this.generateRegexValue(rule);

case 'domain':
return this.generateDomainValue(rule);
return this.generateDomainValue(rule, generationContext);

default:
return `random_${rule.name}_${Math.floor(Math.random() * 1000)}`;
Expand Down Expand Up @@ -302,10 +303,10 @@ export class PairwiseTestDataGenerator {
/**
* Generate a value using domain keywords
*/
generateDomainValue(rule) {
generateDomainValue(rule, generationContext = {}) {
try {
if (this.domainGenerator) {
const result = this.domainGenerator.generateFrom(rule);
const result = this.domainGenerator.generateFrom(rule, generationContext);
return result.data ?? `domain_${rule.ruleSpec}_${Math.floor(Math.random() * 1000)}`;
}
return `domain_${rule.ruleSpec}_${Math.floor(Math.random() * 1000)}`;
Expand Down
7 changes: 4 additions & 3 deletions packages/core/js/data_generation/rulesBasedDataGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ export class RulesBasedDataGenerator {
const headers = fromRules.map((rule) => rule.name);
data.push(headers);

const runStartedAt = new Date();
for (var row = 0; row < thisMany; row++) {
const aRow = this.generateRandomRow(fromRules, options);
const aRow = this.generateRandomRow(fromRules, { ...options, rowIndex: row, runStartedAt });
data.push(aRow);
}

return data;
}

generateRandomRow(fromRules, { constraints = [], maxAttempts = 1 } = {}) {
generateRandomRow(fromRules, { constraints = [], maxAttempts = 1, rowIndex = 0, runStartedAt = new Date() } = {}) {
const rules = Array.isArray(fromRules) ? fromRules : [];
const safeConstraints = Array.isArray(constraints) ? constraints : [];
const committedGeneratorState = this.captureGeneratorStates();
Expand Down Expand Up @@ -73,7 +74,7 @@ export class RulesBasedDataGenerator {
generator = this.defaultGenerator;
}

value = generator.generateFrom(rule);
value = generator.generateFrom(rule, { rowIndex, runStartedAt });

if (value.isError) {
dataGen = '**ERROR**';
Expand Down
Loading