FastAPI backend (Python 3.11, MSSQL ODBC for SambaPOS, Azure DI OCR),
kitchen_db on central PG. React/TS/Vite frontend with navy sidebar layout.
Backend: auth.py (APP_SLUG=kitchen, SimpleNamespace — archive routes use
.kitchen_id/.is_admin without modification), main.py (51 migrations, scheduler,
internal router for KDS bookings feed), api/internal.py, full archive API
(31 routers: invoices, recipes, menus, sambapos, resos, newbook, disputes,
purchase_orders, etc.), models, migrations, OCR pipeline.
kitchen_id pinned to 1 (B1 — single hotel).
Frontend: AuthGate (app=kitchen, token shim for archive compat — B5b pending),
Layout (navy sidebar, 6 sections, Lucide icons, teal --app-primary),
App.tsx (Outlet pattern, UploadApp outside Layout), index.css (full :root block).
strict: false — archive components have type issues; build clean.
Note: 45 archive components call fetch('/api/...') without /kitchen/ prefix
(B5b). Runtime 404s; deferred until after initial testing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
156 lines
3.7 KiB
JavaScript
156 lines
3.7 KiB
JavaScript
'use strict';
|
|
|
|
import stream from 'stream';
|
|
import utils from '../utils.js';
|
|
|
|
const kInternals = Symbol('internals');
|
|
|
|
class AxiosTransformStream extends stream.Transform {
|
|
constructor(options) {
|
|
options = utils.toFlatObject(
|
|
options,
|
|
{
|
|
maxRate: 0,
|
|
chunkSize: 64 * 1024,
|
|
minChunkSize: 100,
|
|
timeWindow: 500,
|
|
ticksRate: 2,
|
|
samplesCount: 15,
|
|
},
|
|
null,
|
|
(prop, source) => {
|
|
return !utils.isUndefined(source[prop]);
|
|
}
|
|
);
|
|
|
|
super({
|
|
readableHighWaterMark: options.chunkSize,
|
|
});
|
|
|
|
const internals = (this[kInternals] = {
|
|
timeWindow: options.timeWindow,
|
|
chunkSize: options.chunkSize,
|
|
maxRate: options.maxRate,
|
|
minChunkSize: options.minChunkSize,
|
|
bytesSeen: 0,
|
|
isCaptured: false,
|
|
notifiedBytesLoaded: 0,
|
|
ts: Date.now(),
|
|
bytes: 0,
|
|
onReadCallback: null,
|
|
});
|
|
|
|
this.on('newListener', (event) => {
|
|
if (event === 'progress') {
|
|
if (!internals.isCaptured) {
|
|
internals.isCaptured = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
_read(size) {
|
|
const internals = this[kInternals];
|
|
|
|
if (internals.onReadCallback) {
|
|
internals.onReadCallback();
|
|
}
|
|
|
|
return super._read(size);
|
|
}
|
|
|
|
_transform(chunk, encoding, callback) {
|
|
const internals = this[kInternals];
|
|
const maxRate = internals.maxRate;
|
|
|
|
const readableHighWaterMark = this.readableHighWaterMark;
|
|
|
|
const timeWindow = internals.timeWindow;
|
|
|
|
const divider = 1000 / timeWindow;
|
|
const bytesThreshold = maxRate / divider;
|
|
const minChunkSize =
|
|
internals.minChunkSize !== false
|
|
? Math.max(internals.minChunkSize, bytesThreshold * 0.01)
|
|
: 0;
|
|
|
|
const pushChunk = (_chunk, _callback) => {
|
|
const bytes = Buffer.byteLength(_chunk);
|
|
internals.bytesSeen += bytes;
|
|
internals.bytes += bytes;
|
|
|
|
internals.isCaptured && this.emit('progress', internals.bytesSeen);
|
|
|
|
if (this.push(_chunk)) {
|
|
process.nextTick(_callback);
|
|
} else {
|
|
internals.onReadCallback = () => {
|
|
internals.onReadCallback = null;
|
|
process.nextTick(_callback);
|
|
};
|
|
}
|
|
};
|
|
|
|
const transformChunk = (_chunk, _callback) => {
|
|
const chunkSize = Buffer.byteLength(_chunk);
|
|
let chunkRemainder = null;
|
|
let maxChunkSize = readableHighWaterMark;
|
|
let bytesLeft;
|
|
let passed = 0;
|
|
|
|
if (maxRate) {
|
|
const now = Date.now();
|
|
|
|
if (!internals.ts || (passed = now - internals.ts) >= timeWindow) {
|
|
internals.ts = now;
|
|
bytesLeft = bytesThreshold - internals.bytes;
|
|
internals.bytes = bytesLeft < 0 ? -bytesLeft : 0;
|
|
passed = 0;
|
|
}
|
|
|
|
bytesLeft = bytesThreshold - internals.bytes;
|
|
}
|
|
|
|
if (maxRate) {
|
|
if (bytesLeft <= 0) {
|
|
// next time window
|
|
return setTimeout(() => {
|
|
_callback(null, _chunk);
|
|
}, timeWindow - passed);
|
|
}
|
|
|
|
if (bytesLeft < maxChunkSize) {
|
|
maxChunkSize = bytesLeft;
|
|
}
|
|
}
|
|
|
|
if (maxChunkSize && chunkSize > maxChunkSize && chunkSize - maxChunkSize > minChunkSize) {
|
|
chunkRemainder = _chunk.subarray(maxChunkSize);
|
|
_chunk = _chunk.subarray(0, maxChunkSize);
|
|
}
|
|
|
|
pushChunk(
|
|
_chunk,
|
|
chunkRemainder
|
|
? () => {
|
|
process.nextTick(_callback, null, chunkRemainder);
|
|
}
|
|
: _callback
|
|
);
|
|
};
|
|
|
|
transformChunk(chunk, function transformNextChunk(err, _chunk) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
if (_chunk) {
|
|
transformChunk(_chunk, transformNextChunk);
|
|
} else {
|
|
callback(null);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export default AxiosTransformStream;
|