naidejs 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/parser.js CHANGED
@@ -102,7 +102,11 @@ export class Parser {
102
102
  type === T.WS || type === T.GROUP || type === T.COOKIE ||
103
103
  type === T.NOT || type === T.AND || type === T.OR ||
104
104
  type === T.IN || type === T.BREAK || type === T.CONTINUE ||
105
- type === T.THROW || type === T.MUT || type === T.PUB;
105
+ type === T.THROW || type === T.MUT || type === T.PUB ||
106
+ type === T.UPLOAD || type === T.SESSION || type === T.VIEW ||
107
+ type === T.SSE || type === T.CACHE || type === T.PATCH || type === T.MID ||
108
+ type === T.VALIDATE || type === T.TEST || type === T.ASSERT ||
109
+ type === T.QUEUE || type === T.JOB || type === T.OPENAPI;
106
110
  }
107
111
 
108
112
  expectPropertyName() {
@@ -201,6 +205,18 @@ export class Parser {
201
205
  if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
202
206
  this.advance();
203
207
  return new ASTNode('CookieDecl');
208
+ case T.VALIDATE:
209
+ if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
210
+ return this.parseValidate();
211
+ case T.TEST:
212
+ if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
213
+ return this.parseTest();
214
+ case T.ASSERT:
215
+ if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
216
+ return this.parseAssert();
217
+ case T.QUEUE:
218
+ if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
219
+ return this.parseQueue();
204
220
  default:
205
221
  if (TYPE_TOKENS.has(tok.type)) {
206
222
  return this.parseTypedVariable();
@@ -252,7 +268,11 @@ export class Parser {
252
268
 
253
269
  parseFunction(isAsync, isPublic) {
254
270
  this.advance(); // fn or fn.async
255
- const name = this.expect(T.IDENT).value;
271
+ const tok = this.advance();
272
+ if (!this.isIdentLike(tok.type)) {
273
+ throw this.error(`Expected function name but got ${tok.type} ('${tok.value}')`, tok);
274
+ }
275
+ const name = tok.value;
256
276
  this.expect(T.LPAREN);
257
277
  const params = this.parseFnParams();
258
278
  this.expect(T.RPAREN);
@@ -343,6 +363,14 @@ export class Parser {
343
363
  const body = this.parseExpression();
344
364
  return new ASTNode('ReturnStatus', { method, statusCode, body });
345
365
  }
366
+ if (method === 'render') {
367
+ const template = this.parseExpression();
368
+ let data = null;
369
+ if (!this.at(T.NEWLINE) && !this.at(T.EOF) && !this.at(T.DEDENT)) {
370
+ data = this.parseExpression();
371
+ }
372
+ return new ASTNode('ReturnRender', { template, data });
373
+ }
346
374
  const value = this.parseExpression();
347
375
  return new ASTNode('ReturnMethod', { method, value });
348
376
  }
@@ -514,10 +542,15 @@ export class Parser {
514
542
  this.skipNewlines();
515
543
 
516
544
  while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
517
- if (this.atAny(T.GET, T.POST, T.PUT, T.DEL)) {
545
+ if (this.atAny(T.GET, T.POST, T.PUT, T.DEL, T.PATCH)) {
518
546
  routes.push(this.parseRoute());
519
547
  } else if (this.at(T.MID)) {
520
- middleware.push(this.parseMiddleware());
548
+ const mid = this.parseMiddleware();
549
+ if (mid.type === 'MiddlewareRef') {
550
+ routes.push(mid);
551
+ } else {
552
+ middleware.push(mid);
553
+ }
521
554
  } else if (this.at(T.CRUD)) {
522
555
  routes.push(this.parseCrud());
523
556
  } else if (this.at(T.AUTH)) {
@@ -535,6 +568,22 @@ export class Parser {
535
568
  } else if (this.at(T.COOKIE)) {
536
569
  this.advance();
537
570
  routes.push(new ASTNode('CookieDecl'));
571
+ } else if (this.at(T.UPLOAD)) {
572
+ routes.push(this.parseUpload());
573
+ } else if (this.at(T.SESSION)) {
574
+ routes.push(this.parseSession());
575
+ } else if (this.at(T.VIEW)) {
576
+ routes.push(this.parseView());
577
+ } else if (this.at(T.SSE)) {
578
+ routes.push(this.parseSse());
579
+ } else if (this.at(T.CACHE)) {
580
+ routes.push(this.parseCache());
581
+ } else if (this.at(T.VALIDATE)) {
582
+ routes.push(this.parseValidate());
583
+ } else if (this.at(T.OPENAPI)) {
584
+ routes.push(this.parseOpenapi());
585
+ } else if (this.at(T.QUEUE)) {
586
+ routes.push(this.parseQueue());
538
587
  } else if (this.at(T.IDENT) && this.peek().value === 'error') {
539
588
  routes.push(this.parseErrorHandler());
540
589
  } else {
@@ -574,16 +623,23 @@ export class Parser {
574
623
  parseMiddleware() {
575
624
  this.advance(); // mid
576
625
  const name = this.expect(T.IDENT).value;
577
- this.expect(T.LPAREN);
578
- const params = [];
579
- while (!this.at(T.RPAREN) && !this.at(T.EOF)) {
580
- params.push(this.expect(T.IDENT).value);
581
- this.match(T.COMMA);
626
+ if (this.at(T.LPAREN)) {
627
+ this.advance();
628
+ const params = [];
629
+ while (!this.at(T.RPAREN) && !this.at(T.EOF)) {
630
+ params.push(this.expect(T.IDENT).value);
631
+ this.match(T.COMMA);
632
+ }
633
+ this.expect(T.RPAREN);
634
+ this.expect(T.COLON);
635
+ const body = this.parseBlock();
636
+ return new ASTNode('Middleware', { name, params, body });
582
637
  }
583
- this.expect(T.RPAREN);
584
- this.expect(T.COLON);
585
- const body = this.parseBlock();
586
- return new ASTNode('Middleware', { name, params, body });
638
+ let path = null;
639
+ if (this.at(T.STRING)) {
640
+ path = this.parseString();
641
+ }
642
+ return new ASTNode('MiddlewareRef', { name, path });
587
643
  }
588
644
 
589
645
  parseModel(isPublic = false) {
@@ -933,6 +989,10 @@ export class Parser {
933
989
  case T.SCHEMA: case T.CRUD: case T.AUTH: case T.CORS:
934
990
  case T.LIMIT: case T.ENV: case T.EVERY: case T.WATCH:
935
991
  case T.STATIC: case T.WS: case T.GROUP: case T.COOKIE:
992
+ case T.UPLOAD: case T.SESSION: case T.VIEW: case T.SSE:
993
+ case T.CACHE: case T.PATCH: case T.MID:
994
+ case T.VALIDATE: case T.TEST: case T.ASSERT:
995
+ case T.QUEUE: case T.JOB: case T.OPENAPI:
936
996
  case T.FROM: case T.AS: case T.IN:
937
997
  this.advance();
938
998
  return new ASTNode('Identifier', { name: tok.value });
@@ -1372,7 +1432,7 @@ export class Parser {
1372
1432
  this.skipNewlines();
1373
1433
 
1374
1434
  while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
1375
- if (this.atAny(T.GET, T.POST, T.PUT, T.DEL)) {
1435
+ if (this.atAny(T.GET, T.POST, T.PUT, T.DEL, T.PATCH)) {
1376
1436
  routes.push(this.parseRoute());
1377
1437
  } else if (this.at(T.MID)) {
1378
1438
  routes.push(this.parseMiddleware());
@@ -1407,4 +1467,108 @@ export class Parser {
1407
1467
  const body = this.parseBlock();
1408
1468
  return new ASTNode('ErrorHandler', { params, body });
1409
1469
  }
1470
+
1471
+ parseUpload() {
1472
+ this.advance(); // upload
1473
+ const path = this.parseString();
1474
+ const fieldName = this.parseString();
1475
+ let params = [];
1476
+ if (this.match(T.LPAREN)) {
1477
+ while (!this.at(T.RPAREN) && !this.at(T.EOF)) {
1478
+ params.push(this.expect(T.IDENT).value);
1479
+ this.match(T.COMMA);
1480
+ }
1481
+ this.expect(T.RPAREN);
1482
+ }
1483
+ this.expect(T.COLON);
1484
+ const body = this.parseBlock();
1485
+ return new ASTNode('UploadDecl', { path, fieldName, params, body });
1486
+ }
1487
+
1488
+ parseSession() {
1489
+ this.advance(); // session
1490
+ const secret = this.parseExpression();
1491
+ return new ASTNode('SessionDecl', { secret });
1492
+ }
1493
+
1494
+ parseView() {
1495
+ this.advance(); // view
1496
+ const dir = this.parseString();
1497
+ return new ASTNode('ViewDecl', { dir });
1498
+ }
1499
+
1500
+ parseSse() {
1501
+ this.advance(); // sse
1502
+ const path = this.parseString();
1503
+ return new ASTNode('SseDecl', { path });
1504
+ }
1505
+
1506
+ parseCache() {
1507
+ this.advance(); // cache
1508
+ const path = this.parseString();
1509
+ const duration = this.parseExpression();
1510
+ return new ASTNode('CacheDecl', { path, duration });
1511
+ }
1512
+
1513
+ parseValidate() {
1514
+ this.advance(); // validate
1515
+ const path = this.parseString();
1516
+ const schemaName = this.expect(T.IDENT).value;
1517
+ return new ASTNode('ValidateDecl', { path, schemaName });
1518
+ }
1519
+
1520
+ parseTest() {
1521
+ this.advance(); // test
1522
+ const name = this.parseExpression();
1523
+ this.expect(T.COLON);
1524
+ const body = this.parseBlock();
1525
+ return new ASTNode('TestDecl', { name, body });
1526
+ }
1527
+
1528
+ parseAssert() {
1529
+ this.advance(); // assert
1530
+ const expr = this.parseExpression();
1531
+ return new ASTNode('AssertStmt', { expr });
1532
+ }
1533
+
1534
+ parseQueue() {
1535
+ this.advance(); // queue
1536
+ const name = this.expect(T.IDENT).value;
1537
+ this.expect(T.COLON);
1538
+ this.skipNewlines();
1539
+ this.expect(T.INDENT);
1540
+
1541
+ const jobs = [];
1542
+ this.skipNewlines();
1543
+
1544
+ while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
1545
+ if (this.at(T.JOB)) {
1546
+ this.advance();
1547
+ const jobName = this.parseString();
1548
+ let params = [];
1549
+ if (this.match(T.LPAREN)) {
1550
+ while (!this.at(T.RPAREN) && !this.at(T.EOF)) {
1551
+ params.push(this.expect(T.IDENT).value);
1552
+ this.match(T.COMMA);
1553
+ }
1554
+ this.expect(T.RPAREN);
1555
+ }
1556
+ this.expect(T.COLON);
1557
+ const body = this.parseBlock();
1558
+ jobs.push({ name: jobName, params, body });
1559
+ } else {
1560
+ this.advance();
1561
+ }
1562
+ this.skipNewlines();
1563
+ }
1564
+ this.match(T.DEDENT);
1565
+
1566
+ return new ASTNode('QueueDecl', { name, jobs });
1567
+ }
1568
+
1569
+ parseOpenapi() {
1570
+ this.advance(); // openapi
1571
+ const path = this.parseString();
1572
+ return new ASTNode('OpenapiDecl', { path });
1573
+ }
1410
1574
  }
package/src/preprocess.js CHANGED
@@ -115,6 +115,8 @@ export function preprocess(source) {
115
115
  out = 'ret.text ' + transformContent(rest.slice(3));
116
116
  } else if (rest.startsWith('.f ')) {
117
117
  out = 'ret.file ' + transformContent(rest.slice(3));
118
+ } else if (rest.startsWith('.v ')) {
119
+ out = 'ret.render ' + transformContent(rest.slice(3));
118
120
  } else {
119
121
  out = rest ? 'ret ' + transformContent(rest) : 'ret';
120
122
  }
@@ -179,6 +181,7 @@ export function preprocess(source) {
179
181
  else if (first === 'P' && second === '"') { out = transformRoute('post', line.slice(1)); }
180
182
  else if (first === 'U' && second === '"') { out = transformRoute('put', line.slice(1)); }
181
183
  else if (first === 'D' && second === '"') { out = transformRoute('del', line.slice(1)); }
184
+ else if (first === 'X' && second === '"') { out = transformRoute('patch', line.slice(1)); }
182
185
  // ^ model
183
186
  else if (first === '^') {
184
187
  const rest = line.slice(1).trim();
package/src/runtime.js CHANGED
@@ -408,6 +408,264 @@ export function cookieParser() {
408
408
  };
409
409
  }
410
410
 
411
+ // ===== Session Middleware (zero-dep, cookie-based) =====
412
+ export function sessionMiddleware(secret) {
413
+ const store = new Map();
414
+ const maxAge = 86400000;
415
+ return (req, res, next) => {
416
+ const cookies = req.headers.cookie || '';
417
+ const sidMatch = cookies.match(/(?:^|;\s*)naide_sid=([^;]+)/);
418
+ let sid = sidMatch ? sidMatch[1] : null;
419
+ if (!sid || !store.has(sid)) {
420
+ sid = randomUUID();
421
+ store.set(sid, {});
422
+ }
423
+ req.session = store.get(sid);
424
+ req.sessionId = sid;
425
+ req.session.destroy = () => { store.delete(sid); };
426
+ const origWriteHead = res.writeHead;
427
+ res.writeHead = function(...args) {
428
+ res.setHeader('Set-Cookie', `naide_sid=${sid}; HttpOnly; Path=/; Max-Age=${maxAge / 1000}; SameSite=Lax`);
429
+ origWriteHead.apply(res, args);
430
+ };
431
+ for (const [id, data] of store) {
432
+ if (data.__ts && Date.now() - data.__ts > maxAge) store.delete(id);
433
+ }
434
+ req.session.__ts = Date.now();
435
+ next();
436
+ };
437
+ }
438
+
439
+ // ===== Template Renderer (zero-dep) =====
440
+ export function createRenderer(viewsDir) {
441
+ return (name, data = {}) => {
442
+ const filePath = viewsDir.replace(/\/$/, '') + '/' + name + (name.includes('.') ? '' : '.html');
443
+ let template = readFileSync(filePath, 'utf-8');
444
+ template = template.replace(/\{\{\s*each\s+(\w+)\s+in\s+(\w+)\s*\}\}([\s\S]*?)\{\{\s*\/each\s*\}\}/g, (_, item, list, block) => {
445
+ const arr = data[list] || [];
446
+ return arr.map(val => block.replace(new RegExp(`\\{\\{\\s*${item}\\b[^}]*\\}\\}`, 'g'), m => {
447
+ const prop = m.match(/\{\{\s*\w+\.(\w+)\s*\}\}/);
448
+ return prop ? String(val[prop[1]] ?? '') : String(val ?? '');
449
+ })).join('');
450
+ });
451
+ template = template.replace(/\{\{\s*if\s+(\w+)\s*\}\}([\s\S]*?)\{\{\s*\/if\s*\}\}/g, (_, key, block) => data[key] ? block : '');
452
+ for (const [key, value] of Object.entries(data)) {
453
+ template = template.replace(new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g'), String(value ?? ''));
454
+ }
455
+ return template;
456
+ };
457
+ }
458
+
459
+ // ===== Cache Middleware (zero-dep) =====
460
+ export function cacheMiddleware(duration) {
461
+ const maxAge = parseMs(duration);
462
+ const cache = new Map();
463
+ return (req, res, next) => {
464
+ if (req.method !== 'GET') return next();
465
+ const key = req.originalUrl || req.url;
466
+ const cached = cache.get(key);
467
+ if (cached && Date.now() - cached.time < maxAge) {
468
+ res.setHeader('X-Cache', 'HIT');
469
+ const ct = cached.contentType || 'application/json';
470
+ res.setHeader('Content-Type', ct);
471
+ return res.end(cached.body);
472
+ }
473
+ const origEnd = res.end;
474
+ const origJson = res.json ? res.json.bind(res) : null;
475
+ if (origJson) {
476
+ res.json = (data) => {
477
+ cache.set(key, { body: JSON.stringify(data), contentType: 'application/json', time: Date.now() });
478
+ res.setHeader('X-Cache', 'MISS');
479
+ origJson(data);
480
+ };
481
+ }
482
+ const origSend = res.send ? res.send.bind(res) : null;
483
+ if (origSend) {
484
+ res.send = (body) => {
485
+ cache.set(key, { body: String(body), contentType: res.getHeader('content-type'), time: Date.now() });
486
+ res.setHeader('X-Cache', 'MISS');
487
+ origSend(body);
488
+ };
489
+ }
490
+ next();
491
+ };
492
+ }
493
+
494
+ // ===== Upload Middleware (zero-dep multipart parser) =====
495
+ export function uploadMiddleware(fieldName, opts = {}) {
496
+ const maxSize = opts.maxSize || 10 * 1024 * 1024;
497
+ return (req, res, next) => {
498
+ const contentType = req.headers['content-type'] || '';
499
+ if (!contentType.startsWith('multipart/form-data')) return next();
500
+ const boundaryMatch = contentType.match(/boundary=(?:"([^"]+)"|([^\s;]+))/);
501
+ if (!boundaryMatch) return next();
502
+ const boundary = boundaryMatch[1] || boundaryMatch[2];
503
+ const chunks = [];
504
+ let size = 0;
505
+ req.on('data', chunk => {
506
+ size += chunk.length;
507
+ if (size > maxSize) { req.destroy(); return res.status(413).json({ error: 'File too large' }); }
508
+ chunks.push(chunk);
509
+ });
510
+ req.on('end', () => {
511
+ try {
512
+ const buffer = Buffer.concat(chunks);
513
+ const { files, fields } = _parseMultipart(buffer, boundary);
514
+ req.files = files;
515
+ req.file = files[fieldName] || files[Object.keys(files)[0]] || null;
516
+ if (!req.body) req.body = {};
517
+ Object.assign(req.body, fields);
518
+ next();
519
+ } catch { res.status(400).json({ error: 'Invalid multipart data' }); }
520
+ });
521
+ req.on('error', () => res.status(400).json({ error: 'Upload failed' }));
522
+ };
523
+ }
524
+
525
+ function _parseMultipart(buffer, boundary) {
526
+ const files = {}, fields = {};
527
+ const delim = Buffer.from(`--${boundary}`);
528
+ let start = buffer.indexOf(delim);
529
+ if (start === -1) return { files, fields };
530
+ start += delim.length + 2;
531
+ const endDelim = Buffer.from(`--${boundary}--`);
532
+ while (start < buffer.length) {
533
+ let end = buffer.indexOf(delim, start);
534
+ if (end === -1) break;
535
+ const part = buffer.slice(start, end - 2);
536
+ const headerEnd = part.indexOf('\r\n\r\n');
537
+ if (headerEnd !== -1) {
538
+ const header = part.slice(0, headerEnd).toString('utf-8');
539
+ const body = part.slice(headerEnd + 4);
540
+ const nameMatch = header.match(/name="([^"]+)"/);
541
+ if (nameMatch) {
542
+ const filenameMatch = header.match(/filename="([^"]+)"/);
543
+ if (filenameMatch) {
544
+ const ctMatch = header.match(/Content-Type:\s*(.+)/i);
545
+ files[nameMatch[1]] = { filename: filenameMatch[1], contentType: ctMatch ? ctMatch[1].trim() : 'application/octet-stream', data: body, size: body.length };
546
+ } else {
547
+ fields[nameMatch[1]] = body.toString('utf-8');
548
+ }
549
+ }
550
+ }
551
+ start = end + delim.length;
552
+ if (buffer.slice(end, end + endDelim.length).equals(endDelim)) break;
553
+ start += 2;
554
+ }
555
+ return { files, fields };
556
+ }
557
+
558
+ // ===== SSE Manager (zero-dep) =====
559
+ export function createSseManager() {
560
+ const clients = new Set();
561
+ return {
562
+ handler() {
563
+ return (req, res) => {
564
+ res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*' });
565
+ res.write(':\n\n');
566
+ const client = { res, id: randomUUID() };
567
+ clients.add(client);
568
+ req.on('close', () => clients.delete(client));
569
+ };
570
+ },
571
+ send(data, event) {
572
+ const payload = typeof data === 'string' ? data : JSON.stringify(data);
573
+ for (const client of clients) {
574
+ if (event) client.res.write(`event: ${event}\n`);
575
+ client.res.write(`data: ${payload}\n\n`);
576
+ }
577
+ },
578
+ broadcast(data, event) { this.send(data, event); },
579
+ get count() { return clients.size; }
580
+ };
581
+ }
582
+
583
+ // ===== Validate Middleware =====
584
+ export function validateMiddleware(schema) {
585
+ return (req, res, next) => {
586
+ if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
587
+ const result = schema.validate(req.body || {});
588
+ if (!result.valid) {
589
+ return res.status(400).json({ errors: result.errors });
590
+ }
591
+ req.body = result.data;
592
+ }
593
+ next();
594
+ };
595
+ }
596
+
597
+ // ===== Job Queue (in-memory async) =====
598
+ export function createQueue() {
599
+ const handlers = new Map();
600
+ const pending = [];
601
+ let running = false;
602
+
603
+ async function process() {
604
+ if (running) return;
605
+ running = true;
606
+ while (pending.length > 0) {
607
+ const { name, data, resolve, reject } = pending.shift();
608
+ const handler = handlers.get(name);
609
+ if (handler) {
610
+ try { await handler(data); resolve(); } catch (e) { console.error('[NAIDE Queue]', e.message); reject(e); }
611
+ } else {
612
+ reject(new Error(`No handler for job: ${name}`));
613
+ }
614
+ }
615
+ running = false;
616
+ }
617
+
618
+ return {
619
+ register(name, fn) { handlers.set(name, fn); },
620
+ add(name, data) {
621
+ return new Promise((resolve, reject) => {
622
+ pending.push({ name, data, resolve, reject });
623
+ process();
624
+ });
625
+ },
626
+ get size() { return pending.length; }
627
+ };
628
+ }
629
+
630
+ // ===== OpenAPI Spec Builder =====
631
+ export function buildOpenApiSpec(schemas) {
632
+ const spec = {
633
+ openapi: '3.1.0',
634
+ info: { title: 'API', version: '1.0.0' },
635
+ paths: {},
636
+ components: { schemas: {} }
637
+ };
638
+ for (const schema of schemas) {
639
+ const properties = {};
640
+ const required = [];
641
+ for (const [fieldName, fieldDef] of Object.entries(schema.fields)) {
642
+ const prop = {};
643
+ switch (fieldDef.type) {
644
+ case 'id': prop.type = 'string'; prop.format = 'uuid'; break;
645
+ case 'string': prop.type = 'string'; break;
646
+ case 'integer': prop.type = 'integer'; break;
647
+ case 'number': prop.type = 'number'; break;
648
+ case 'boolean': prop.type = 'boolean'; break;
649
+ case 'timestamp': prop.type = 'string'; prop.format = 'date-time'; break;
650
+ case 'enum':
651
+ prop.type = 'string';
652
+ if (fieldDef.values) prop.enum = fieldDef.values;
653
+ break;
654
+ default: prop.type = 'string';
655
+ }
656
+ if (fieldDef.min !== undefined) prop.minimum = fieldDef.min;
657
+ if (fieldDef.max !== undefined) prop.maximum = fieldDef.max;
658
+ if (fieldDef.email) prop.format = 'email';
659
+ if (fieldDef.url) prop.format = 'uri';
660
+ properties[fieldName] = prop;
661
+ if (fieldDef.required) required.push(fieldName);
662
+ }
663
+ spec.components.schemas[schema.name] = { type: 'object', properties };
664
+ if (required.length > 0) spec.components.schemas[schema.name].required = required;
665
+ }
666
+ return spec;
667
+ }
668
+
411
669
  // ===== Helpers =====
412
670
  function parseMs(str) {
413
671
  if (typeof str === 'number') return str;
package/src/tokens.js CHANGED
@@ -69,6 +69,18 @@ export const T = {
69
69
  WS: 'WS',
70
70
  GROUP: 'GROUP',
71
71
  COOKIE: 'COOKIE',
72
+ UPLOAD: 'UPLOAD',
73
+ SESSION: 'SESSION',
74
+ VIEW: 'VIEW',
75
+ SSE: 'SSE',
76
+ CACHE: 'CACHE',
77
+ PATCH: 'PATCH',
78
+ VALIDATE: 'VALIDATE',
79
+ TEST: 'TEST',
80
+ ASSERT: 'ASSERT',
81
+ QUEUE: 'QUEUE',
82
+ JOB: 'JOB',
83
+ OPENAPI: 'OPENAPI',
72
84
 
73
85
  // Operators
74
86
  ASSIGN: 'ASSIGN',
@@ -166,6 +178,18 @@ export const KEYWORDS = {
166
178
  'ws': T.WS,
167
179
  'group': T.GROUP,
168
180
  'cookie': T.COOKIE,
181
+ 'upload': T.UPLOAD,
182
+ 'session': T.SESSION,
183
+ 'view': T.VIEW,
184
+ 'sse': T.SSE,
185
+ 'cache': T.CACHE,
186
+ 'patch': T.PATCH,
187
+ 'validate': T.VALIDATE,
188
+ 'test': T.TEST,
189
+ 'assert': T.ASSERT,
190
+ 'queue': T.QUEUE,
191
+ 'job': T.JOB,
192
+ 'openapi': T.OPENAPI,
169
193
  'true': T.BOOL,
170
194
  'false': T.BOOL,
171
195
  'null': T.NULL,