Baseline: the 2021 bulk order update tool (before the rebuild)

Related: Results: Bulk Order Update Tool · Rebuilding a Shopify Bulk Order Update Tool the Right Way · All projects

This page records the state of the 2021 tool in legacy/ before the rebuild. The tool reads a CSV of new quantities and changes the matching lines on store orders through the Toy-Shop Admin GraphQL API.

Recorded: 2026-10-21. No client name, store or order data is on this page.

Each fact has a source:

  • Code: I read it in the source. I did not run that path.
  • Run: I ran it on 2026-10-21, on Node v22.22.3, in a copy of legacy/.

How it works

CheckResultSource
ShapeAn Express web app with an upload form (Handlebars views). The upload route does all the work inside 1 HTTP requestCode
StorageMongoDB (Mongoose), only for the success and failure records. Each upload deletes all earlier records firstCode
InputA CSV with the columns Order #, Description, SKU, Final Qty. Rows with an empty order, SKU or quantity are skippedCode
BatchingRows go in chunks of 20. Each chunk runs 4 steps in order: find the order ID, begin an edit, set the quantity, commitCode
Edit sessions1 edit session for each row, not for each order. So each row costs 4 API callsCode
Diff before writeNone. Every row is written, also when the quantity does not changeCode

Rate limits and speed

CheckResultSource
Throttle handlingNo throttleStatus reads and no THROTTLED handling. Fixed sleeps only: in each step, row n waits n × 3 s, × 4 s, × 5 s and × 4.5 sCode
Time for 1 chunk of 20 rowsAbout 5 minutes in sleeps alone (19 × 16.5 s = 313.5 s)Code (calculated)
Time for 339 rowsAbout 89 minutes in sleeps alone (16 chunks of 20, then 1 of 19)Code (calculated)
API calls for 339 rows1,356 (4 for each row)Code (calculated)

Failures

CheckResultSource
RetriesNone. Any error becomes a failure record that says “please update manually”Code
Hang riskEach step resolves its promise only in the finally block of the last row. That block reads results[index], but a failed row is not in results, so the indexes shift. After 1 failure, the finally block can throw, and the request never endsCode
Empty stepIf a step has 0 rows (for example, every order lookup failed), its promise never resolves, and the request never endsCode
Large filesThe upload is read with createReadStream(...).on('data'), and each chunk of the stream is parsed as its own CSV with a header row. A file larger than 1 stream chunk (64 KB) can lose or mix up rows. Each chunk can also call res.redirect, which sends a second responseCode
Saving the uploadpromisify(fs.writeFile) is called with an extra callback, so the await never resolves. The work runs inside that callback insteadCode
RerunA rerun writes every row again, with 4 API calls each. Because the quantities are absolute, the result is the same, but nothing is skippedCode

Health of the code

CheckResultSource
npm installWorks. 508 packages in the treeRun
npm audit41 vulnerabilities: 4 critical (handlebars, minimist, mongoose, proxy-addr), 24 high, 9 moderate, 4 lowRun
Testsnpm test: 1 passing, 3 failing. The tests expect fields (poNumber) that Record no longer has. The second test path (utils/__tests__) does not existRun
Startnpm start with no .env: it prints “server running”, then crashes because MONGODB_URI is missing. A full run needs MongoDB and a real store, so I did not run an uploadRun
Dead codesrc/draft-orders.js (a draft-order path) is never called. formatDate in utils/helpers.js uses an undefined variable (date)Code
Types and lintPlain JavaScript (CommonJS), ESLint with the Airbnb config. No typesCode

What the rebuild must fix

  1. Wait only as long as the rate limit needs, from throttleStatus, not with fixed sleeps.
  2. Diff before writing, and use 1 edit session for each order.
  3. Retry transient errors, and keep permanent errors in 1 place with the reason.
  4. Never hang: every order must end as done or failed.
  5. Make a rerun safe and cheap.

The results of the rebuild are in Results: Bulk Order Update Tool.