Skip to content
Open
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
10 changes: 10 additions & 0 deletions sdk/typescript-schema/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ npm run gen-schema

This ensures that all generated files are up to date with your changes.

### Generating Minified Types

To generate a single minified TypeScript file containing all type definitions from `audience.ts` and its dependencies:

```bash
npm run gen-minified-types
```

This will create `schema/audience-types.min.ts` with all types consolidated into a single file without imports, exports, or comments. This is useful for documentation, distribution, or embedding in other tools.

### Updating Title Paths in add_titles_to_schema.sh

If you add, remove, or rename types or titles in the schema, you will need to update the relevant JSONPath mappings in [`scripts/add_titles_to_schema.sh`](./scripts/add_titles_to_schema.sh) to reflect these changes. This script relies on hardcoded paths to insert titles into the schema, so keeping these paths accurate is necessary for correct schema generation.
Expand Down
15 changes: 12 additions & 3 deletions sdk/typescript-schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
"description": "TypeScript schema definitions for mParticle Audiences",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./schema.json": "./dist/schema/audience-schema.json",
"./types.min": "./dist/schema/audience-types.min.ts"
},
"files": [
"dist"
],
Expand All @@ -12,9 +20,10 @@
"node": ">=18.20.4"
},
"scripts": {
"build": "tsc",
"build": "tsc && mkdir -p dist/schema && cp schema/audience-schema.json dist/schema/ && cp schema/audience-types.min.ts dist/schema/",
"semantic-release": "semantic-release",
"gen-schema": "scripts/gen_schema_with_titles.sh",
"gen-minified-types": "npx ts-node scripts/generate-minified-types.ts",
"gen-schema": "scripts/gen_schema_with_titles.sh && yarn gen-minified-types",
"test": "jest",
"test:watch": "jest --watch"
},
Expand All @@ -32,4 +41,4 @@
"typescript": "5.0.4"
},
"sideEffects": false
}
}
5 changes: 5 additions & 0 deletions sdk/typescript-schema/schema/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# audience-schema.json
JSON schema for an audience. Used to generate python.

# audience-types.min.ts
A "minified" typescript file that includes all the types/enums/etc for the current audience definition. Useful when needing to pass this structure to LLMs with minimum characters.
1 change: 1 addition & 0 deletions sdk/typescript-schema/schema/audience-types.min.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const CURRENT_VERSION = '1.3.3'; type Version = `${number}.${number}.${number}` | `${number}.${number}.${number}-${string}`; type DateUnit = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; type AbsoluteDate = { 'absolute': string }; type RelativeDate = { 'relative': { offset: number, unit: DateUnit, boundary?: 'start' | 'end' } }; type DateOperand = { date: AbsoluteDate } | { date: RelativeDate }; type ModelPath = { model: string, path: string }; enum UnaryOperator { Null = 'null', NotNull = 'not_null', Exists = 'exists', NotExists = 'not_exists' }; type BinaryOperator = 'equals' | 'not_equals' | 'less_than' | 'less_than_equal' | 'greater_than' | 'greater_than_equal' | 'matches' | 'contains' | 'not_contains' | 'starts_with' | 'not_starts_with' | 'ends_with' | 'not_ends_with' | 'in' | 'not_in'; type ListOperator = 'contains' | 'between' | 'match_any' | 'match_all' | 'in' | 'not_in'; type AudienceOperator = 'in' | 'not_in'; type ArithmeticOperator = 'plus' | 'minus' | 'multiply' | 'divide' | 'mod'; type AggregationOperator = 'min' | 'max' | 'sum' | 'avg' | 'list' | 'count'; type LocationOperator = 'within' | 'equals'; type LogicalOperator = 'and' | 'or'; type AudienceOperand = { audience: string }; type ModelOperand = { model: string }; type Operand = boolean | number | string | DateOperand | ModelPath | ModelOperand | AudienceOperand | { operator: AggregationOperator, group_by_model: string, operand: Operand, condition?: Expression }; type Expression = { operator: UnaryOperator, operand: Operand } | { operator: BinaryOperator, left: Operand, right: Operand } | { operator: LogicalOperator, expressions: Expression[] }; type Audience = { schema_version: Version, audience: Expression }
262 changes: 262 additions & 0 deletions sdk/typescript-schema/scripts/generate-minified-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
#!/usr/bin/env ts-node

import * as fs from 'fs';
import * as path from 'path';

interface ImportInfo {
imported: string[];
from: string;
}

/**
* Extracts import statements from a TypeScript file
*/
function extractImports(content: string): ImportInfo[] {
const imports: ImportInfo[] = [];
const importRegex = /import\s+(?:{([^}]+)}|(\w+))\s+from\s+['"]([^'"]+)['"]/g;

let match;
while ((match = importRegex.exec(content)) !== null) {
const namedImports = match[1];
const defaultImport = match[2];
const from = match[3];

if (namedImports) {
const imported = namedImports.split(',').map(i => i.trim());
imports.push({ imported, from });
} else if (defaultImport) {
imports.push({ imported: [defaultImport], from });
}
}

return imports;
}

/**
* Removes import and export keywords from content, and filters out non-type declarations
*/
function removeImportsAndExports(content: string): string {
// Remove import statements
content = content.replace(/import\s+(?:{[^}]+}|\w+)\s+from\s+['"][^'"]+['"];?\n?/g, '');

// Remove export keyword but keep the type/enum/interface declarations only
content = content.replace(/export\s+(type|enum|interface)/g, '$1');

// Split content into lines and filter out non-type declarations
const lines = content.split('\n');
const filteredLines: string[] = [];
let inConstDeclaration = false;
let braceCount = 0;

for (const line of lines) {
// Check if this line starts a const, class, or function declaration
if (/^(export\s+)?(const|class|function)\s+/.test(line.trim())) {
inConstDeclaration = true;
braceCount = (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;
if (braceCount === 0 && line.includes(';')) {
// Single line const declaration
inConstDeclaration = false;
}
continue; // Skip this line
}

if (inConstDeclaration) {
// Count braces to know when the declaration ends
braceCount += (line.match(/{/g) || []).length;
braceCount -= (line.match(/}/g) || []).length;

if (braceCount <= 0) {
inConstDeclaration = false;
}
continue; // Skip this line
}

// Keep type, enum, and interface declarations
filteredLines.push(line);
}

content = filteredLines.join('\n');

return content;
}

/**
* Removes comments from TypeScript code
*/
function removeComments(content: string): string {
// Remove multi-line comments (including JSDoc)
content = content.replace(/\/\*\*?[\s\S]*?\*\//g, '');

// Remove single-line comments
content = content.replace(/\/\/.*$/gm, '');

return content;
}

/**
* Minifies TypeScript content by removing extra whitespace
*/
function minify(content: string): string {
// Remove multiple blank lines
content = content.replace(/\n\s*\n\s*\n/g, '\n');

// Remove trailing whitespace
content = content.replace(/[ \t]+$/gm, '');

// Remove leading whitespace from lines (but preserve indentation structure)
// This is conservative to maintain readability

return content.trim();
}

/**
* Converts multi-line content to a single line with semicolons
*/
function convertToSingleLine(content: string): string {
// Remove all newlines and excessive whitespace
content = content.replace(/\n+/g, ' ');

// Collapse multiple spaces into one
content = content.replace(/\s+/g, ' ');

// Ensure proper semicolon separation between statements
// Add semicolon before type/interface/enum keywords if not already present
content = content.replace(/\s+(type|interface|enum)\s+/g, '; $1 ');

// Add semicolon after closing braces if not already present
content = content.replace(/}\s*(?=[a-zA-Z])/g, '}; ');

// Clean up any double semicolons
content = content.replace(/;+/g, ';');

// Clean up semicolon at the start if present
content = content.replace(/^\s*;\s*/, '');

// Remove spaces around semicolons for compactness
content = content.replace(/\s*;\s*/g, '; ');

// Replace all double quotes with single quotes
content = content.replace(/"/g, "'");

return content.trim();
}

/**
* Resolves a relative import path to an absolute file path
*/
function resolveImportPath(currentFilePath: string, importPath: string): string {
const currentDir = path.dirname(currentFilePath);
const resolved = path.resolve(currentDir, importPath);

// Try with .ts extension if it doesn't exist
if (fs.existsSync(resolved + '.ts')) {
return resolved + '.ts';
}
if (fs.existsSync(resolved)) {
return resolved;
}

throw new Error(`Cannot resolve import: ${importPath} from ${currentFilePath}`);
}

/**
* Recursively collects all type definitions from a file and its imports
*/
function collectTypeDefinitions(
filePath: string,
visited: Set<string> = new Set(),
definitions: Map<string, string> = new Map()
): Map<string, string> {
// Avoid circular dependencies
const absolutePath = path.resolve(filePath);
if (visited.has(absolutePath)) {
return definitions;
}
visited.add(absolutePath);

// Read the file
const content = fs.readFileSync(absolutePath, 'utf-8');

// Extract imports and recursively process them first
const imports = extractImports(content);
for (const importInfo of imports) {
try {
const importedFilePath = resolveImportPath(absolutePath, importInfo.from);
collectTypeDefinitions(importedFilePath, visited, definitions);
} catch (e) {
console.warn(`Warning: Could not resolve import ${importInfo.from} in ${filePath}`);
}
}

// Process current file content
let processedContent = removeImportsAndExports(content);
processedContent = removeComments(processedContent);
processedContent = minify(processedContent);

// Only add if there's actual content
if (processedContent.trim()) {
definitions.set(absolutePath, processedContent);
}

return definitions;
}

/**
* Extracts the version value from version.ts
*/
function extractVersion(rootDir: string): string {
const versionFile = path.join(rootDir, 'version.ts');
try {
const content = fs.readFileSync(versionFile, 'utf-8');
const versionMatch = content.match(/VERSION\s*=\s*['"]([^'"]+)['"]/);
if (versionMatch) {
return versionMatch[1];
}
} catch (e) {
console.warn('Warning: Could not read version from version.ts');
}
return 'unknown';
}

/**
* Main function
*/
function main() {
const rootDir = path.resolve(__dirname, '..');
const audienceFile = path.join(rootDir, 'audience.ts');
const outputDir = path.join(rootDir, 'schema');
const outputFile = path.join(outputDir, 'audience-types.min.ts');

console.log('Collecting type definitions from:', audienceFile);

// Collect all type definitions
const definitions = collectTypeDefinitions(audienceFile);

// Extract version
const version = extractVersion(rootDir);

// Prepend version constant and combine all definitions
const versionExport = `export const CURRENT_VERSION = "${version}";`;
const allDefinitions = Array.from(definitions.values()).join('\n');
const combined = versionExport + '\n' + allDefinitions;

// Convert to single line with semicolons
const singleLine = convertToSingleLine(combined);

// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}

// Write the output
fs.writeFileSync(outputFile, singleLine, 'utf-8');

console.log(`Generated minified types at: ${outputFile}`);
console.log(`Version: ${version}`);
console.log(`Total files processed: ${definitions.size}`);
console.log(`Output size: ${singleLine.length} characters`);
}

// Run the script
main();

3 changes: 2 additions & 1 deletion sdk/typescript-schema/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"exclude": [
"node_modules",
"dist",
"scripts"
"scripts",
"schema"
]
}