UNPKG

open-lineage-client

Version:

TypeScript/JavaScript client for creating and sending OpenLineage standard events.

400 lines (295 loc) 12.8 kB
# OpenLineage Client for TypeScript/JavaScript This library provides a client for creating and sending OpenLineage events from TypeScript and JavaScript applications. It aims to offer functionality similar to the official OpenLineage [Python](https://github.com/OpenLineage/OpenLineage/tree/main/client/python) and [Java](https://github.com/OpenLineage/OpenLineage/tree/main/client/java) clients. The project was recently converted to TypeScript, providing enhanced type safety and developer experience, while maintaining the core functionality. ## Overview OpenLineage is an open standard for data lineage collection and analysis. This client helps instrument your data processing jobs (e.g., ETL/ELT pipelines, data transformations) to emit lineage events according to the OpenLineage specification. **Key Features:** * **Type-Safe:** Written in TypeScript for robust integration. * **Event Builders:** Fluent builders for constructing `Run`, `Job`, and `Dataset` information, including standard `Facets`. * **Pluggable Transports:** Supports different methods for sending events: * `ConsoleTransport`: Prints events to the console (useful for debugging). * `HttpTransport`: Sends events to an OpenLineage-compatible HTTP endpoint (like Marquez). * **Configuration:** Flexible configuration options via YAML file or direct instantiation. * **Standard Compliance:** Adheres to the OpenLineage [specification](https://openlineage.io/docs/). ## Prerequisites * **Node.js:** Version 18 or higher is recommended. The library is tested with Node.js v22. ## Installation This package is published on npm ([https://www.npmjs.com/package/open-lineage-client](https://www.npmjs.com/package/open-lineage-client)). Install the client using npm or your preferred package manager: ```bash npm install open-lineage-client ``` ## Usage Using the client involves three main steps: Initialization, Creating Events, and Sending Events. ### 1. Initialization First, create an instance of `OpenLineageClient`. You can provide a specific transport or let the client attempt to load configuration from a file. **Default (File/Console Transport):** If an `openlineage.yaml` file exists in the project root (where the application is run), the client will attempt to load the configuration from it. If the file is not found or is invalid, it defaults to `ConsoleTransport`. ```typescript import { OpenLineageClient } from 'open-lineage-client'; const client = new OpenLineageClient(); ``` **Explicit Transport (e.g., HTTP):** You can explicitly configure a transport, such as `HttpTransport`. ```typescript import { OpenLineageClient } from 'open-lineage-client'; import { HttpTransport, HttpConfig } from 'open-lineage-client'; const httpConfig = new HttpConfig("http://localhost:5000/api/v1/lineage"); const transport = new HttpTransport(httpConfig); const client = new OpenLineageClient(transport); ``` See the [Configuration](#configuration) section for more details on setting up transports. ### 2. Creating Events Use the provided builders to construct OpenLineage events. The primary builder is `RunEventBuilder`. ```typescript import { RunEventBuilder, JobBuilder, RunBuilder, DatasetBuilder, EventType } from 'open-lineage-client'; import { v4 as uuidv4 } from 'uuid'; // Example: using uuid for run IDs // Define Job details const job = new JobBuilder() .setNamespace("my-namespace") .setName("my-job") .build(); // Define Run details const run = new RunBuilder() .setRunId(uuidv4()) // Generate a unique run ID .build(); // Define Input/Output Datasets (optional) const inputDataset = new DatasetBuilder() .setNamespace("my-data-namespace") .setName("my-input-table") .build(); const outputDataset = new DatasetBuilder() .setNamespace("my-data-namespace") .setName("my-output-table") .build(); // Build the START event // Use your application/library identifier as producer const producer = "https://github.com/my-org/my-app/v/1.0.0"; // Specify the OpenLineage schema version you are using const schemaURL = "https://openlineage.io/spec/1-0-5/OpenLineage.json"; // Use the latest relevant version const startEvent = new RunEventBuilder() .setEventType(EventType.START) .setEventTime(new Date().toISOString()) .setProducer(producer) .setSchemaURL(schemaURL) .setJob(job) .setRun(run) .setInputs([inputDataset]) // Optional .setOutputs([outputDataset]) // Optional .build(); // Later, build the COMPLETE event (or FAIL) const completeEvent = new RunEventBuilder() .setEventType(EventType.COMPLETE) // Or EventType.FAIL .setEventTime(new Date().toISOString()) .setProducer(producer) .setSchemaURL(schemaURL) .setJob(job) .setRun(run) .setInputs([inputDataset]) // Usually repeated from START .setOutputs([outputDataset]) // Usually repeated from START .build(); ``` ### 3. Sending Events Use the `emit` method of the client instance to send the constructed events. Note that `emit` returns a Promise. ```typescript async function sendEvents() { // Send the START event await client.emit(startEvent); // ... your job processing logic ... // Send the COMPLETE event await client.emit(completeEvent); } sendEvents().catch(console.error); ``` ## Configuration The `OpenLineageClient` can be configured in the following ways: 1. **YAML File (Recommended):** Create a file named `openlineage.yaml` in the root directory from where your application is executed. The client automatically detects and loads this file if no transport is explicitly provided during initialization. 2. **Direct Instantiation:** Configure and pass a transport instance (`ConsoleTransport` or `HttpTransport`) directly to the `OpenLineageClient` constructor, as shown in the [Initialization](#1-initialization) examples. ### YAML Configuration (`openlineage.yaml`) The YAML file defines the transport type and its specific configuration. **Example: Console Transport** ```yaml transport: type: console # No further config needed for console ``` **Example: HTTP Transport** ```yaml transport: type: http config: # Required: The URL of the OpenLineage HTTP endpoint url: http://localhost:5000/api/v1/lineage # Optional: Authentication Token (Bearer) # token: Bearer your-api-key # Optional: Axios request options (passed directly to Axios) options: timeout: 5000 # Request timeout in milliseconds (default: depends on Axios) headers: X-Custom-Header: 'custom-value' # Optional: Retry configuration (nested under options) retry: maxRetries: 5 # Default: 3 initialRetryDelay: 1000 # Default: 1000 ms maxRetryDelay: 30000 # Default: 30000 ms # retryStatusCodes: [429, 503] # Optional: Override default retry codes [408, 429, 500, 502, 503, 504] ``` **Supported `transport.type` values:** * `http`: Sends events via HTTP POST requests. * `console`: Prints events to the standard output. ### Environment Variables Currently, configuration via environment variables is not directly supported for overriding YAML settings. ## Examples Here's a more complete example demonstrating sending START and COMPLETE events using configuration loaded from `openlineage.yaml`. **1. Create `openlineage.yaml`:** ```yaml # openlineage.yaml transport: type: http config: url: http://localhost:5000/api/v1/lineage # Replace with your Marquez/OL endpoint options: timeout: 10000 ``` **2. Your Application Code (`myJob.ts`):** ```typescript import { OpenLineageClient, RunEventBuilder, JobBuilder, RunBuilder, DatasetBuilder, EventType } from 'open-lineage-client'; import { v4 as uuidv4 } from 'uuid'; // Using uuid library for run IDs async function runMyJob() { // Initialize client - will load from openlineage.yaml if present const client = new OpenLineageClient(); const producer = "https://github.com/my-org/my-app/v/1.0.0"; const schemaURL = "https://openlineage.io/spec/1-0-5/OpenLineage.json"; // Use latest relevant schema const job = new JobBuilder().setNamespace("etl-namespace").setName("daily-report-job").build(); const run = new RunBuilder().setRunId(uuidv4()).build(); // Generate unique run ID const input = new DatasetBuilder().setNamespace("db-schema").setName("source_table").build(); const output = new DatasetBuilder().setNamespace("db-schema").setName("report_table").build(); // Create START event const startEvent = new RunEventBuilder() .setEventType(EventType.START) .setEventTime(new Date().toISOString()) .setProducer(producer) .setSchemaURL(schemaURL) .setJob(job) .setRun(run) .setInputs([input]) .setOutputs([output]) .build(); console.log('Sending START event...'); await client.emit(startEvent); // emit is async try { // --- Simulate Job Execution --- console.log(`Processing data for run ${run.runId}...`); await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate work console.log('Processing complete.'); // --- Job Execution End --- // Create COMPLETE event const completeEvent = new RunEventBuilder() .setEventType(EventType.COMPLETE) .setEventTime(new Date().toISOString()) .setProducer(producer) .setSchemaURL(schemaURL) .setJob(job) .setRun(run) .setInputs([input]) .setOutputs([output]) .build(); console.log('Sending COMPLETE event...'); await client.emit(completeEvent); } catch (error) { console.error('Job failed:', error); // Create FAIL event const failEvent = new RunEventBuilder() .setEventType(EventType.FAIL) .setEventTime(new Date().toISOString()) .setProducer(producer) .setSchemaURL(schemaURL) .setJob(job) .setRun(run) // Optionally include error details using facets (e.g., ErrorMessageRunFacet) // .addFacet('errorMessage', new ErrorMessageRunFacet(String(error.message), 'TypeScript', error.stack)) .build(); console.log('Sending FAIL event...'); await client.emit(failEvent); } } runMyJob().catch(console.error); ``` ## Development This section provides guidance for developing and testing the client library itself. If you just want to use the client, see the [Installation](#installation) and [Usage](#usage) sections. ### Local Setup 1. **Clone the repository:** ```bash git clone https://github.com/valensim/OpenLineageJSClient.git cd OpenLineageJSClient/ts ``` 2. **Install dependencies:** ```bash npm install ``` 3. **Build the project:** ```bash npm run build ``` ### Running Tests Tests are written using [Vitest](https://vitest.dev/). 1. **Navigate to the TypeScript directory:** ```bash cd ts # If not already there ``` 2. **Set up Environment Variables:** Ensure a `.env` file exists in the `ts/` directory (one should exist from the clone). This file configures test behaviors: ```dotenv # ts/.env MARQUEZ_UP=false DEBUG=false ``` * `MARQUEZ_UP`: Set to `true` to enable integration tests that require a running instance of a Marquez server (configured via `openlineage.yaml`). Defaults to `false`, skipping these tests. * `DEBUG`: Set to `true` to enable more verbose logging or specific debug behaviors within the tests. Defaults to `false`. 3. **Run tests:** ```bash npm test ``` To run tests with coverage reports: ```bash npm run coverage ``` ### Building To transpile the TypeScript code to JavaScript in the `dist` directory: ```bash # Ensure you are in the ts/ directory npm run build ``` ### Linting and Formatting This project uses ESLint for linting and Prettier for code formatting. Run these from the `ts/` directory. ```bash # Check formatting npm run format:check # Apply formatting npm run format # Run linter npm run lint # Fix lint errors automatically npm run lint:fix ``` ### Contributing Contributions are welcome! Please feel free to open an issue on the GitHub repository to discuss potential changes or submit a pull request. ## TypeScript Usage This library is written in TypeScript and includes type definitions (`.d.ts` files) in the distributed package. * **TypeScript Projects:** You get full type safety and autocompletion when importing the client. * **JavaScript Projects:** You can use this library in plain JavaScript projects as well. While you won't get compile-time type checking, the library is fully functional. Modern editors might still leverage the included type definitions for better IntelliSense. ## CI/CD Continuous Integration is set up using GitLab CI (see `.gitlab-ci.yml`). (Note: This section might need updates based on current CI setup).