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>
261 lines
6.8 KiB
JavaScript
261 lines
6.8 KiB
JavaScript
const util = require('./util')
|
|
|
|
module.exports = function stringify (value, replacer, space) {
|
|
const stack = []
|
|
let indent = ''
|
|
let propertyList
|
|
let replacerFunc
|
|
let gap = ''
|
|
let quote
|
|
|
|
if (
|
|
replacer != null &&
|
|
typeof replacer === 'object' &&
|
|
!Array.isArray(replacer)
|
|
) {
|
|
space = replacer.space
|
|
quote = replacer.quote
|
|
replacer = replacer.replacer
|
|
}
|
|
|
|
if (typeof replacer === 'function') {
|
|
replacerFunc = replacer
|
|
} else if (Array.isArray(replacer)) {
|
|
propertyList = []
|
|
for (const v of replacer) {
|
|
let item
|
|
|
|
if (typeof v === 'string') {
|
|
item = v
|
|
} else if (
|
|
typeof v === 'number' ||
|
|
v instanceof String ||
|
|
v instanceof Number
|
|
) {
|
|
item = String(v)
|
|
}
|
|
|
|
if (item !== undefined && propertyList.indexOf(item) < 0) {
|
|
propertyList.push(item)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (space instanceof Number) {
|
|
space = Number(space)
|
|
} else if (space instanceof String) {
|
|
space = String(space)
|
|
}
|
|
|
|
if (typeof space === 'number') {
|
|
if (space > 0) {
|
|
space = Math.min(10, Math.floor(space))
|
|
gap = ' '.substr(0, space)
|
|
}
|
|
} else if (typeof space === 'string') {
|
|
gap = space.substr(0, 10)
|
|
}
|
|
|
|
return serializeProperty('', {'': value})
|
|
|
|
function serializeProperty (key, holder) {
|
|
let value = holder[key]
|
|
if (value != null) {
|
|
if (typeof value.toJSON5 === 'function') {
|
|
value = value.toJSON5(key)
|
|
} else if (typeof value.toJSON === 'function') {
|
|
value = value.toJSON(key)
|
|
}
|
|
}
|
|
|
|
if (replacerFunc) {
|
|
value = replacerFunc.call(holder, key, value)
|
|
}
|
|
|
|
if (value instanceof Number) {
|
|
value = Number(value)
|
|
} else if (value instanceof String) {
|
|
value = String(value)
|
|
} else if (value instanceof Boolean) {
|
|
value = value.valueOf()
|
|
}
|
|
|
|
switch (value) {
|
|
case null: return 'null'
|
|
case true: return 'true'
|
|
case false: return 'false'
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
return quoteString(value, false)
|
|
}
|
|
|
|
if (typeof value === 'number') {
|
|
return String(value)
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
return Array.isArray(value) ? serializeArray(value) : serializeObject(value)
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
function quoteString (value) {
|
|
const quotes = {
|
|
"'": 0.1,
|
|
'"': 0.2,
|
|
}
|
|
|
|
const replacements = {
|
|
"'": "\\'",
|
|
'"': '\\"',
|
|
'\\': '\\\\',
|
|
'\b': '\\b',
|
|
'\f': '\\f',
|
|
'\n': '\\n',
|
|
'\r': '\\r',
|
|
'\t': '\\t',
|
|
'\v': '\\v',
|
|
'\0': '\\0',
|
|
'\u2028': '\\u2028',
|
|
'\u2029': '\\u2029',
|
|
}
|
|
|
|
let product = ''
|
|
|
|
for (let i = 0; i < value.length; i++) {
|
|
const c = value[i]
|
|
switch (c) {
|
|
case "'":
|
|
case '"':
|
|
quotes[c]++
|
|
product += c
|
|
continue
|
|
|
|
case '\0':
|
|
if (util.isDigit(value[i + 1])) {
|
|
product += '\\x00'
|
|
continue
|
|
}
|
|
}
|
|
|
|
if (replacements[c]) {
|
|
product += replacements[c]
|
|
continue
|
|
}
|
|
|
|
if (c < ' ') {
|
|
let hexString = c.charCodeAt(0).toString(16)
|
|
product += '\\x' + ('00' + hexString).substring(hexString.length)
|
|
continue
|
|
}
|
|
|
|
product += c
|
|
}
|
|
|
|
const quoteChar = quote || Object.keys(quotes).reduce((a, b) => (quotes[a] < quotes[b]) ? a : b)
|
|
|
|
product = product.replace(new RegExp(quoteChar, 'g'), replacements[quoteChar])
|
|
|
|
return quoteChar + product + quoteChar
|
|
}
|
|
|
|
function serializeObject (value) {
|
|
if (stack.indexOf(value) >= 0) {
|
|
throw TypeError('Converting circular structure to JSON5')
|
|
}
|
|
|
|
stack.push(value)
|
|
|
|
let stepback = indent
|
|
indent = indent + gap
|
|
|
|
let keys = propertyList || Object.keys(value)
|
|
let partial = []
|
|
for (const key of keys) {
|
|
const propertyString = serializeProperty(key, value)
|
|
if (propertyString !== undefined) {
|
|
let member = serializeKey(key) + ':'
|
|
if (gap !== '') {
|
|
member += ' '
|
|
}
|
|
member += propertyString
|
|
partial.push(member)
|
|
}
|
|
}
|
|
|
|
let final
|
|
if (partial.length === 0) {
|
|
final = '{}'
|
|
} else {
|
|
let properties
|
|
if (gap === '') {
|
|
properties = partial.join(',')
|
|
final = '{' + properties + '}'
|
|
} else {
|
|
let separator = ',\n' + indent
|
|
properties = partial.join(separator)
|
|
final = '{\n' + indent + properties + ',\n' + stepback + '}'
|
|
}
|
|
}
|
|
|
|
stack.pop()
|
|
indent = stepback
|
|
return final
|
|
}
|
|
|
|
function serializeKey (key) {
|
|
if (key.length === 0) {
|
|
return quoteString(key, true)
|
|
}
|
|
|
|
const firstChar = String.fromCodePoint(key.codePointAt(0))
|
|
if (!util.isIdStartChar(firstChar)) {
|
|
return quoteString(key, true)
|
|
}
|
|
|
|
for (let i = firstChar.length; i < key.length; i++) {
|
|
if (!util.isIdContinueChar(String.fromCodePoint(key.codePointAt(i)))) {
|
|
return quoteString(key, true)
|
|
}
|
|
}
|
|
|
|
return key
|
|
}
|
|
|
|
function serializeArray (value) {
|
|
if (stack.indexOf(value) >= 0) {
|
|
throw TypeError('Converting circular structure to JSON5')
|
|
}
|
|
|
|
stack.push(value)
|
|
|
|
let stepback = indent
|
|
indent = indent + gap
|
|
|
|
let partial = []
|
|
for (let i = 0; i < value.length; i++) {
|
|
const propertyString = serializeProperty(String(i), value)
|
|
partial.push((propertyString !== undefined) ? propertyString : 'null')
|
|
}
|
|
|
|
let final
|
|
if (partial.length === 0) {
|
|
final = '[]'
|
|
} else {
|
|
if (gap === '') {
|
|
let properties = partial.join(',')
|
|
final = '[' + properties + ']'
|
|
} else {
|
|
let separator = ',\n' + indent
|
|
let properties = partial.join(separator)
|
|
final = '[\n' + indent + properties + ',\n' + stepback + ']'
|
|
}
|
|
}
|
|
|
|
stack.pop()
|
|
indent = stepback
|
|
return final
|
|
}
|
|
}
|