npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

abap-sql

v0.1.0

Published

Prepare ABAP Open SQL for SAP's ADT data-preview console. Fixes the dialect, enforces the limit.

Readme

abap-sql

Prepare ABAP Open SQL for SAP's ADT data-preview console. Fixes the dialect, enforces the limit.

These three statements fail on a live system:

| Sent | Response | | --- | --- | | SELECT SUM(netwr) FROM vbap | 400 Unknown column name "SUM(NETWR)" | | SELECT COUNT(DISTINCT kunnr) FROM vbak | 400 ")" is invalid here (due to grammar) | | SELECT a FROM t WHERE (x = '1' OR y = '2') | 400 ") " was expected here. |

Three messages, three apparent rules, one actual cause: the parser wants a space on the inside of every parenthesis. It even says so in the third message. All three pass once spaced.

This package knows that rule, and the handful of others that cost people afternoons.

npm install abap-sql

Driving an agent rather than writing code? The same rules ship as an MCP server: sap-abap-sql-mcp.

Why this endpoint matters

The data-preview console (/sap/bc/adt/datapreview/freestyle) is the widest read surface an on-premise ABAP system exposes over HTTP. It reaches any table, needs no database user, and is where every ADT client and agent bridge ends up. It is also strict in ways its error messages do not explain.

Reaching one from outside the network is a separate problem, and a worse one: SICF nodes, service registration, system versus dialog users, and a tunnel that opens no inbound port. There is a free write-up of what actually works: connecting an on-premise S/4HANA over HTTP.

Use

import { prepare, AbapSqlError } from "abap-sql";

prepare("SELECT COUNT(DISTINCT kunnr) FROM vbak WHERE vtweg = '10'");
// → "SELECT COUNT( DISTINCT kunnr ) FROM vbak WHERE vtweg = '10'"

prepare("SELECT vbeln FROM vbak ORDER BY vbeln DESC");
// throws AbapSqlError: Sorting is ORDER BY x DESCENDING, not DESC.

prepare runs the checks, applies what is mechanical, and refuses what is not. Lower-level pieces are exported too: normalize, lint, isReadOnly, tokenize.

What it fixes for you

Spacing inside parentheses, everywhere it is needed at once: aggregates (SUM( netwr )), DISTINCT (COUNT( DISTINCT kunnr )), subqueries (IN ( SELECT … )), and boolean groups (( a OR b )). COUNT(*) is left compact, because the parser takes it either way and every character counts against the ceiling.

What it refuses, with the correction

| Written | Told | | --- | --- | | LIMIT 10 | ABAP Open SQL has no LIMIT. Cap the result with the reader's row limit instead. | | ORDER BY a DESC | Sorting is ORDER BY x DESCENDING, not DESC. | | p.vbeln | Qualify join fields with ~, not a dot: write p~vbeln, not p.vbeln. | | DELETE FROM … | Only SELECT is allowed. |

Each returns a stable rule id, so you can suppress or test one of them.

The 255-character ceiling

The console rejects 256 characters and accepts 255, measured across the whole statement with newlines collapsed. Wrapping buys nothing: the server's message names "line 1", but a 300-character statement across five short lines fails identically.

prepare measures after normalizing, because spacing lengthens the statement. Checking the input instead lets a statement pass locally and fail on the system.

Why a lexer and not a regex

Because a parenthesis inside 'PUMP (SPARE)' is data, and rewriting it changes the answer rather than the shape. ABAP has two string forms, an escape, and a comment form, and all four have to be understood before a single character is moved:

normalize("SELECT a FROM t WHERE x = 'PUMP (SPARE)'");   // unchanged
normalize("SELECT a FROM t WHERE x = `PUMP (SPARE)`");   // unchanged, backtick form
normalize("SELECT a FROM t WHERE x = 'it''s'");        // unchanged, doubled quote is an escape
normalize("SELECT a FROM t WHERE x = 'a  b'");         // inner spaces survive the collapse

The same reasoning applies to the read-only check. A scan of the raw text for write verbs refuses this, which is a read:

isReadOnly("SELECT belnr FROM bkpf WHERE bktxt = 'Update pending'");  // true

Tokenizing is done by abaplint's lexer, which is the one correct ABAP tokenizer in TypeScript. It is reached at a private path because the package does not export it from the root; test/contract.test.ts asserts every assumption we make about it against the installed copy, so an upstream move fails a test here rather than corrupting a query quietly.

Scope

This prepares statements for the ADT data-preview console. It does not parse responses, hold a connection, or know anything about your system. Pair it with whatever ADT client you already have.

Open SQL written inside an ABAP program is a related but different dialect: it has INTO TABLE, UP TO n ROWS, @host variables, and no character ceiling. The paren rule and the sort keyword still apply there; the ceiling does not, so pass prepare(sql, { maxChars: Infinity }) if you are working on program source.

Credits

Tokenization by abaplint (MIT). The rules come from statements that actually failed against a live on-premise system, with the system's own error text recorded next to each fix.

Built by Daslab. We connect agents to on-premise ERP systems.

License

MIT