xiaoshu-runtime-compat.mjs 4.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import {readPressureRuntimeSource} from './read-pressure-runtime.mjs';
  2. // The multi-app worker provides a deliberately small REST facade, not the Parse SDK.
  3. // Adapt only Xiaoshu gateway-local bindings inside its own isolated worker.
  4. export async function resolveXiaoshuRuntime(nativeParse, nativePsql, request, loadEnvironment) {
  5. if (typeof nativeParse.User === 'function' && typeof nativePsql.tx === 'function') {
  6. return { Parse: nativeParse, Psql: nativePsql };
  7. }
  8. const load = loadEnvironment || (async () => {
  9. const { workerData } = await import('node:worker_threads');
  10. const { createRequire } = await import('node:module');
  11. const require = createRequire('/opt/future-server/package.json');
  12. const SDK = require('parse/node');
  13. const coreModule = require('parse/lib/node/CoreManager.js');
  14. return { SDK, CoreManager: coreModule.default || coreModule, workerData };
  15. });
  16. const { SDK, CoreManager, workerData } = await load();
  17. const db = nativePsql.db;
  18. if (workerData?.companyId !== '7pIbDBJmKx' || workerData.config?.appId !== '7pIbDBJmKx_main'
  19. || nativeParse.applicationId !== workerData.config.appId
  20. || !nativeParse.masterKey || nativeParse.masterKey !== workerData.config.masterKey
  21. || typeof workerData.token !== 'string' || !workerData.token
  22. || !/^http:\/\/127\.0\.0\.1:\d+\/data\/?$/.test(nativeParse.serverURL)
  23. || typeof db?.tx !== 'function') {
  24. throw new Error('Unsupported Xiaoshu isolated runtime');
  25. }
  26. // One tenant per worker thread; these SDK settings never cross tenant workers.
  27. SDK.initialize(nativeParse.applicationId, undefined, nativeParse.masterKey);
  28. SDK.serverURL = nativeParse.serverURL;
  29. CoreManager.set('REQUEST_HEADERS', { 'x-fmode-worker-token': workerData.token });
  30. CoreManager.set('REQUEST_ATTEMPT_LIMIT', 1);
  31. for (const [field, className] of [['user', '_User'], ['company', 'Company'], ['account', 'Account']]) {
  32. const value = request[field];
  33. if (!value) continue;
  34. const json = typeof value.toJSON === 'function' ? value.toJSON() : value;
  35. if (!json.objectId) throw new Error('Invalid Xiaoshu runtime identity');
  36. request[field] = SDK.Object.fromJSON({ ...json, className });
  37. }
  38. return { Parse: SDK, Psql: db };
  39. }
  40. export function withXiaoshuRuntime(code,options={}) {
  41. const marker = '// XIAOSHU_ISOLATED_RUNTIME_COMPAT_V1';
  42. if (code.includes(marker)) return code;
  43. const pattern = /async function handler\s*\(/g;
  44. if ([...code.matchAll(pattern)].length !== 1) throw new Error('Expected exactly one async handler');
  45. return code.replace(pattern, 'async function xiaoshuBusinessHandler(') + '\n' + marker + '\n'
  46. + resolveXiaoshuRuntime.toString() + '\n' + (options.readPressure ? readPressureRuntimeSource() : '') + `
  47. async function handler(request, response) {
  48. try {
  49. const runtime = await resolveXiaoshuRuntime(Parse, Psql, request);
  50. Parse = runtime.Parse;
  51. Psql = runtime.Psql;
  52. } catch (error) {
  53. console.error('[XiaoshuRuntime] ' + String(error?.message || error));
  54. return response.status(503).json({ success: false, error: 'runtime_compatibility_error', message: '小树服务暂时不可用,请稍后重试', retcode: -1, retmsg: '小树服务暂时不可用,请稍后重试', result: null });
  55. }
  56. ${options.readPressure?`const pressureStartedAt=Date.now();
  57. if(typeof response.on==='function')response.on('finish',()=>{const durationMs=Date.now()-pressureStartedAt;if(response.statusCode>=400||durationMs>=1000||Math.random()<0.01)console.info(JSON.stringify({event:'xiaoshu_read_pressure',operation:readPressurePolicy(${JSON.stringify(options.readPressure)},request.body?.params||{}).operation,status:response.statusCode,durationMs}));});
  58. try {
  59. return await dispatchReadPressure(Psql,request,response,${JSON.stringify(options.readPressure)},async bounded=>{const original=Psql;Psql=bounded;try{return await xiaoshuBusinessHandler(request,response);}finally{Psql=original;}},async()=>{
  60. const input=inputOf(request),context=await auth(request),payload=input.payload&&typeof input.payload==='object'?input.payload:{};
  61. if(context.storeId)await prepareStoreRequest(context,input,payload);
  62. requireRole(context,['ops-manager','ops-staff','ops-auditor']);
  63. if(context.current.get('passwordResetRequired')===true)fail(403,'请先修改临时密码');
  64. return{company:companyIdOf(context,input),scope:JSON.stringify([roleOf(context),context.storeId||0])};
  65. });
  66. }catch(error){
  67. if(response.headersSent)return response;
  68. const status=Number(error.status||(error.code==='57014'?408:500)),message=status===429?'查询较多,请稍后重试':status===408?'查询暂未完成,请稍后重试':status<500?String(error.message||'请求失败'):'数据加载失败,请稍后重试';
  69. return response.status(status).json({success:false,error:status===429?'read_capacity_busy':'read_failed',message,retcode:-1,retmsg:message,...(status===429?{retryAfterMs:2000}:{})});
  70. }`:'return xiaoshuBusinessHandler(request, response);'}
  71. }
  72. `;
  73. }