naider 1.13.0 → 1.14.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/README.md CHANGED
@@ -727,6 +727,35 @@ i18n "locales/":
727
727
 
728
728
  Usage: `i18n.t("greeting.hello")`, `i18n.setLang("ja")`.
729
729
 
730
+ ## Push Notifications
731
+
732
+ ```python
733
+ push env.VAPID_PUBLIC env.VAPID_PRIVATE:
734
+ endpoint "/subscribe"
735
+ ```
736
+
737
+ Usage: `push.send(subscription, "Title", "Body")`. Compiles to web-push (Node.js) or pywebpush (Python).
738
+
739
+ ## Full-Text Search
740
+
741
+ ```python
742
+ search "meilisearch" "http://localhost:7700" env.MEILI_KEY:
743
+ index "products"
744
+ ```
745
+
746
+ Usage: `search.query("keyword")`, `search.add(docs)`. Supports Meilisearch and Elasticsearch.
747
+
748
+ ## Image Processing
749
+
750
+ ```python
751
+ image "photo.jpg" -> "output.jpg":
752
+ resize 800 600
753
+ grayscale
754
+ watermark "logo.png"
755
+ ```
756
+
757
+ Operations: `resize`, `crop`, `rotate`, `blur`, `grayscale`, `flip`, `watermark`, `format`. Compiles to sharp (Node.js) or Pillow (Python).
758
+
730
759
  ## Environment Variables
731
760
 
732
761
  ```python
package/SPEC.naide CHANGED
@@ -564,3 +564,23 @@ i18n "locales/":
564
564
  lang "ja" "ja.json"
565
565
  # usage: i18n.t("greeting.hello")
566
566
  # usage: i18n.setLang("ja")
567
+
568
+
569
+ # ---- Push通知 ----
570
+ push env.VAPID_PUBLIC env.VAPID_PRIVATE:
571
+ endpoint "/subscribe"
572
+ # usage: push.send(subscription, "Title", "Body")
573
+
574
+
575
+ # ---- 全文検索 ----
576
+ search "meilisearch" "http://localhost:7700" env.MEILI_KEY:
577
+ index "products"
578
+ # usage: search.query("keyword")
579
+ # usage: search.add([{id: 1, name: "Item"}])
580
+
581
+
582
+ # ---- 画像処理 ----
583
+ image "photo.jpg" -> "output.jpg":
584
+ resize 800 600
585
+ grayscale
586
+ watermark "logo.png"
package/lsp/server.js CHANGED
@@ -289,7 +289,7 @@ function getCompletions() {
289
289
  'cors', 'auth', 'crud', 'limit', 'cookie', 'session', 'static', 'ws', 'sse',
290
290
  'cache', 'view', 'upload', 'group', 'validate', 'openapi', 'error', 'mid', 'prompt',
291
291
  'page', 'cli', 'mail', 'graphql', 'desktop', 'screen',
292
- 'oauth', 'pay', 'storage', 'pdf', 'i18n',
292
+ 'oauth', 'pay', 'storage', 'pdf', 'i18n', 'push', 'search', 'image',
293
293
  ];
294
294
  const builtins = [
295
295
  { label: 'uuid()', detail: 'Generate UUID v4', insertText: 'uuid()' },
@@ -351,6 +351,9 @@ const HOVER_DOCS = {
351
351
  'storage': '**storage** — Cloud storage (S3/GCS)\n```naide\nstorage "s3" env.BUCKET env.KEY env.SECRET:\n region "ap-northeast-1"\n```\nUsage: `storage.upload(key, body)`, `storage.download(key)`',
352
352
  'pdf': '**pdf** — PDF generation\n```naide\npdf "report.pdf":\n title "Report"\n text "Hello"\n```',
353
353
  'i18n': '**i18n** — Internationalization\n```naide\ni18n "locales/":\n default "en"\n lang "en" "en.json"\n lang "ja" "ja.json"\n```\nUsage: `i18n.t("key")`, `i18n.setLang("ja")`',
354
+ 'push': '**push** — Push notifications\n```naide\npush env.VAPID_PUBLIC env.VAPID_PRIVATE:\n endpoint "/subscribe"\n```\nUsage: `push.send(subscription, title, body)`',
355
+ 'search': '**search** — Full-text search\n```naide\nsearch "meilisearch" "http://localhost:7700" env.KEY:\n index "products"\n```\nUsage: `search.query("keyword")`, `search.add(docs)`',
356
+ 'image': '**image** — Image processing\n```naide\nimage "input.jpg" -> "output.jpg":\n resize 800 600\n grayscale\n watermark "logo.png"\n```',
354
357
  };
355
358
 
356
359
  function getHover(params) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naider",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "NAIDE - Node AI Development Environment. AI-specialized language with built-in server, auth, DB, SQL, AI/LLM, WebSocket, bots (Discord/Slack/Telegram/LINE), CLI, HTML, email, GraphQL, desktop, mobile, and more — transpiles to Node.js/Python/Bun.",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -153,6 +153,9 @@ export class PythonGenerator {
153
153
  case 'StorageDecl': return this.visitStorage(node);
154
154
  case 'PdfDecl': return this.visitPdf(node);
155
155
  case 'I18nDecl': return this.visitI18n(node);
156
+ case 'PushDecl': return this.visitPush(node);
157
+ case 'SearchDecl': return this.visitSearch(node);
158
+ case 'ImageDecl': return this.visitImage(node);
156
159
  default:
157
160
  this.emit(`# unknown: ${node.type}`);
158
161
  }
@@ -2146,4 +2149,103 @@ export class PythonGenerator {
2146
2149
  this.emit(`i18n = I18n()`);
2147
2150
  this.emitRaw('');
2148
2151
  }
2152
+
2153
+ // ===== Push =====
2154
+ visitPush(node) {
2155
+ const publicKey = this.expr(node.publicKey);
2156
+ const privateKey = this.expr(node.privateKey);
2157
+ this.addFromImport('pywebpush', 'webpush');
2158
+ this.emitRaw('');
2159
+ this.emit(`class Push:`);
2160
+ this.indent++;
2161
+ this.emit(`VAPID_PUBLIC = ${publicKey}`);
2162
+ this.emit(`VAPID_PRIVATE = ${privateKey}`);
2163
+ this.emit(`@staticmethod`);
2164
+ this.emit(`def send(subscription, title, body):`);
2165
+ this.indent++;
2166
+ this.emit(`import json`);
2167
+ this.emit(`webpush(subscription_info=subscription, data=json.dumps({'title': title, 'body': body}), vapid_private_key=Push.VAPID_PRIVATE, vapid_claims={'sub': 'mailto:noreply@example.com'})`);
2168
+ this.indent--;
2169
+ this.indent--;
2170
+ this.emit(`push = Push()`);
2171
+ this.emitRaw('');
2172
+ }
2173
+
2174
+ // ===== Search =====
2175
+ visitSearch(node) {
2176
+ const engine = this.rawString(node.engine);
2177
+ const host = this.expr(node.host);
2178
+ const apiKey = this.expr(node.apiKey);
2179
+ const index = node.index ? this.rawString(node.index) : 'default';
2180
+
2181
+ if (engine === 'meilisearch') {
2182
+ this.addImport('meilisearch');
2183
+ this.emitRaw('');
2184
+ this.emit(`__search_client = meilisearch.Client(${host}, ${apiKey})`);
2185
+ this.emit(`__search_index = __search_client.index(${JSON.stringify(index)})`);
2186
+ this.emitRaw('');
2187
+ this.emit(`class Search:`);
2188
+ this.indent++;
2189
+ this.emit(`@staticmethod`);
2190
+ this.emit(`def query(q, **opts): return __search_index.search(q, opts)`);
2191
+ this.emit(`@staticmethod`);
2192
+ this.emit(`def add(docs): return __search_index.add_documents(docs)`);
2193
+ this.emit(`@staticmethod`);
2194
+ this.emit(`def remove(doc_id): return __search_index.delete_document(doc_id)`);
2195
+ this.indent--;
2196
+ } else {
2197
+ this.addFromImport('elasticsearch', 'Elasticsearch');
2198
+ this.emitRaw('');
2199
+ this.emit(`__es = Elasticsearch(${host}, api_key=${apiKey})`);
2200
+ this.emitRaw('');
2201
+ this.emit(`class Search:`);
2202
+ this.indent++;
2203
+ this.emit(`@staticmethod`);
2204
+ this.emit(`def query(q, **opts): return __es.search(index=${JSON.stringify(index)}, query={'match': {'_all': q}}, **opts)`);
2205
+ this.emit(`@staticmethod`);
2206
+ this.emit(`def add(doc): return __es.index(index=${JSON.stringify(index)}, body=doc)`);
2207
+ this.emit(`@staticmethod`);
2208
+ this.emit(`def remove(doc_id): return __es.delete(index=${JSON.stringify(index)}, id=doc_id)`);
2209
+ this.indent--;
2210
+ }
2211
+ this.emit(`search = Search()`);
2212
+ this.emitRaw('');
2213
+ }
2214
+
2215
+ // ===== Image =====
2216
+ visitImage(node) {
2217
+ const input = this.expr(node.input);
2218
+ const output = node.output ? this.expr(node.output) : input;
2219
+ this.addFromImport('PIL', 'Image as PILImage');
2220
+ this.emitRaw('');
2221
+ this.emit(`__img = PILImage.open(${input})`);
2222
+
2223
+ for (const op of node.operations) {
2224
+ if (op.op === 'resize') {
2225
+ const w = op.args[0] || 800;
2226
+ const h = op.args[1] || 600;
2227
+ this.emit(`__img = __img.resize((${w}, ${h}))`);
2228
+ } else if (op.op === 'crop') {
2229
+ const l = op.args[0] || 0, t = op.args[1] || 0, r = op.args[2] || 100, b = op.args[3] || 100;
2230
+ this.emit(`__img = __img.crop((${l}, ${t}, ${r}, ${b}))`);
2231
+ } else if (op.op === 'rotate') {
2232
+ this.emit(`__img = __img.rotate(${op.args[0] || 90})`);
2233
+ } else if (op.op === 'blur') {
2234
+ this.addFromImport('PIL.ImageFilter', 'GaussianBlur');
2235
+ this.emit(`__img = __img.filter(GaussianBlur(radius=${op.args[0] || 5}))`);
2236
+ } else if (op.op === 'grayscale' || op.op === 'greyscale') {
2237
+ this.emit(`__img = __img.convert('L')`);
2238
+ } else if (op.op === 'flip') {
2239
+ this.emit(`__img = __img.transpose(PILImage.FLIP_TOP_BOTTOM)`);
2240
+ } else if (op.op === 'watermark') {
2241
+ const wm = op.args[0] ? this.rawString(op.args[0]) : 'watermark.png';
2242
+ this.emit(`__wm = PILImage.open(${JSON.stringify(wm)})`);
2243
+ this.emit(`__img.paste(__wm, (0, 0), __wm)`);
2244
+ }
2245
+ }
2246
+
2247
+ this.emit(`__img.save(${output})`);
2248
+ this.emit(`print(f"Processed: {${output}}")`);
2249
+ this.emitRaw('');
2250
+ }
2149
2251
  }
package/src/generator.js CHANGED
@@ -153,6 +153,9 @@ export class Generator {
153
153
  case 'StorageDecl': return this.visitStorage(node);
154
154
  case 'PdfDecl': return this.visitPdf(node);
155
155
  case 'I18nDecl': return this.visitI18n(node);
156
+ case 'PushDecl': return this.visitPush(node);
157
+ case 'SearchDecl': return this.visitSearch(node);
158
+ case 'ImageDecl': return this.visitImage(node);
156
159
  default:
157
160
  this.emit(`/* unknown: ${node.type} */`);
158
161
  }
@@ -2062,4 +2065,110 @@ export class Generator {
2062
2065
  this.emit(`};`);
2063
2066
  this.emitRaw('');
2064
2067
  }
2068
+
2069
+ // ===== Push Notifications =====
2070
+
2071
+ visitPush(node) {
2072
+ const publicKey = this.expr(node.publicKey);
2073
+ const privateKey = this.expr(node.privateKey);
2074
+ const endpoint = node.endpoint ? this.rawPageString(node.endpoint) : '/subscribe';
2075
+
2076
+ this.emit(`import webpush from 'web-push';`);
2077
+ this.emitRaw('');
2078
+ this.emit(`webpush.setVapidDetails('mailto:noreply@example.com', ${publicKey}, ${privateKey});`);
2079
+ this.emitRaw('');
2080
+ this.emit(`const push = {`);
2081
+ this.indent++;
2082
+ this.emit(`async send(subscription, title, body, data = {}) {`);
2083
+ this.indent++;
2084
+ this.emit(`return webpush.sendNotification(subscription, JSON.stringify({ title, body, data }));`);
2085
+ this.indent--;
2086
+ this.emit(`},`);
2087
+ this.emit(`async sendAll(subscriptions, title, body, data = {}) {`);
2088
+ this.indent++;
2089
+ this.emit(`return Promise.allSettled(subscriptions.map(sub => push.send(sub, title, body, data)));`);
2090
+ this.indent--;
2091
+ this.emit(`},`);
2092
+ this.indent--;
2093
+ this.emit(`};`);
2094
+ this.emitRaw('');
2095
+ }
2096
+
2097
+ // ===== Search =====
2098
+
2099
+ visitSearch(node) {
2100
+ const engine = this.rawPageString(node.engine);
2101
+ const host = this.expr(node.host);
2102
+ const apiKey = this.expr(node.apiKey);
2103
+ const index = node.index ? this.rawPageString(node.index) : 'default';
2104
+
2105
+ if (engine === 'meilisearch') {
2106
+ this.emit(`import { MeiliSearch } from 'meilisearch';`);
2107
+ this.emitRaw('');
2108
+ this.emit(`const __searchClient = new MeiliSearch({ host: ${host}, apiKey: ${apiKey} });`);
2109
+ this.emit(`const __searchIndex = __searchClient.index(${JSON.stringify(index)});`);
2110
+ this.emitRaw('');
2111
+ this.emit(`const search = {`);
2112
+ this.indent++;
2113
+ this.emit(`async query(q, opts = {}) { return __searchIndex.search(q, opts); },`);
2114
+ this.emit(`async add(docs) { return __searchIndex.addDocuments(docs); },`);
2115
+ this.emit(`async remove(id) { return __searchIndex.deleteDocument(id); },`);
2116
+ this.emit(`async update(docs) { return __searchIndex.updateDocuments(docs); },`);
2117
+ this.indent--;
2118
+ this.emit(`};`);
2119
+ } else {
2120
+ this.emit(`import { Client } from '@elastic/elasticsearch';`);
2121
+ this.emitRaw('');
2122
+ this.emit(`const __esClient = new Client({ node: ${host}, auth: { apiKey: ${apiKey} } });`);
2123
+ this.emitRaw('');
2124
+ this.emit(`const search = {`);
2125
+ this.indent++;
2126
+ this.emit(`async query(q, opts = {}) { return __esClient.search({ index: ${JSON.stringify(index)}, query: { match: { _all: q } }, ...opts }); },`);
2127
+ this.emit(`async add(doc) { return __esClient.index({ index: ${JSON.stringify(index)}, body: doc }); },`);
2128
+ this.emit(`async remove(id) { return __esClient.delete({ index: ${JSON.stringify(index)}, id }); },`);
2129
+ this.indent--;
2130
+ this.emit(`};`);
2131
+ }
2132
+ this.emitRaw('');
2133
+ }
2134
+
2135
+ // ===== Image Processing =====
2136
+
2137
+ visitImage(node) {
2138
+ const input = this.expr(node.input);
2139
+ const output = node.output ? this.expr(node.output) : input;
2140
+
2141
+ this.emit(`import sharp from 'sharp';`);
2142
+ this.emitRaw('');
2143
+ this.emit(`let __img = sharp(${input});`);
2144
+
2145
+ for (const op of node.operations) {
2146
+ if (op.op === 'resize') {
2147
+ const w = op.args[0] || 800;
2148
+ const h = op.args[1] || null;
2149
+ this.emit(`__img = __img.resize(${w}${h ? ', ' + h : ''});`);
2150
+ } else if (op.op === 'crop') {
2151
+ const l = op.args[0] || 0, t = op.args[1] || 0, w = op.args[2] || 100, h = op.args[3] || 100;
2152
+ this.emit(`__img = __img.extract({ left: ${l}, top: ${t}, width: ${w}, height: ${h} });`);
2153
+ } else if (op.op === 'watermark') {
2154
+ const wm = op.args[0] ? (typeof op.args[0] === 'object' ? this.rawPageString(op.args[0]) : op.args[0]) : 'watermark.png';
2155
+ this.emit(`__img = __img.composite([{ input: ${JSON.stringify(wm)}, gravity: 'southeast' }]);`);
2156
+ } else if (op.op === 'rotate') {
2157
+ this.emit(`__img = __img.rotate(${op.args[0] || 90});`);
2158
+ } else if (op.op === 'blur') {
2159
+ this.emit(`__img = __img.blur(${op.args[0] || 5});`);
2160
+ } else if (op.op === 'grayscale' || op.op === 'greyscale') {
2161
+ this.emit(`__img = __img.grayscale();`);
2162
+ } else if (op.op === 'flip') {
2163
+ this.emit(`__img = __img.flip();`);
2164
+ } else if (op.op === 'format') {
2165
+ const fmt = op.args[0] ? (typeof op.args[0] === 'object' ? this.rawPageString(op.args[0]) : op.args[0]) : 'png';
2166
+ this.emit(`__img = __img.toFormat(${JSON.stringify(fmt)});`);
2167
+ }
2168
+ }
2169
+
2170
+ this.emit(`await __img.toFile(${output});`);
2171
+ this.emit(`console.log('Processed:', ${output});`);
2172
+ this.emitRaw('');
2173
+ }
2065
2174
  }
package/src/parser.js CHANGED
@@ -113,7 +113,8 @@ export class Parser {
113
113
  type === T.PAGE || type === T.CLI_APP || type === T.MAIL ||
114
114
  type === T.GRAPHQL || type === T.DESKTOP || type === T.SCREEN ||
115
115
  type === T.OAUTH || type === T.PAY || type === T.STORAGE ||
116
- type === T.PDF || type === T.I18N;
116
+ type === T.PDF || type === T.I18N ||
117
+ type === T.PUSH || type === T.SEARCH || type === T.IMAGE;
117
118
  }
118
119
 
119
120
  expectPropertyName() {
@@ -180,6 +181,9 @@ export class Parser {
180
181
  case T.STORAGE: return this.parseStorage();
181
182
  case T.PDF: return this.parsePdf();
182
183
  case T.I18N: return this.parseI18n();
184
+ case T.PUSH: return this.parsePush();
185
+ case T.SEARCH: return this.parseSearch();
186
+ case T.IMAGE: return this.parseImage();
183
187
  case T.MODEL: return this.parseModel();
184
188
  case T.ON: return this.parseOn();
185
189
  case T.LOG: return this.parseLog();
@@ -1156,6 +1160,7 @@ export class Parser {
1156
1160
  case T.PAGE: case T.CLI_APP: case T.MAIL:
1157
1161
  case T.GRAPHQL: case T.DESKTOP: case T.SCREEN:
1158
1162
  case T.OAUTH: case T.PAY: case T.STORAGE: case T.PDF: case T.I18N:
1163
+ case T.PUSH: case T.SEARCH: case T.IMAGE:
1159
1164
  case T.FROM: case T.AS: case T.IN:
1160
1165
  this.advance();
1161
1166
  return new ASTNode('Identifier', { name: tok.value });
@@ -2068,4 +2073,79 @@ export class Parser {
2068
2073
  if (this.at(T.DEDENT)) this.advance();
2069
2074
  return new ASTNode('I18nDecl', { dir, defaultLang, langs });
2070
2075
  }
2076
+
2077
+ // push "vapid_public" "vapid_private":
2078
+ // endpoint "/subscribe"
2079
+ parsePush() {
2080
+ this.expect(T.PUSH);
2081
+ const publicKey = this.parseExpression();
2082
+ const privateKey = this.parseExpression();
2083
+ this.expect(T.COLON);
2084
+ this.skipNewlines();
2085
+ this.expect(T.INDENT);
2086
+ let endpoint = null;
2087
+ while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
2088
+ this.skipNewlines();
2089
+ if (this.at(T.DEDENT) || this.at(T.EOF)) break;
2090
+ const kw = this.peek().value;
2091
+ if (kw === 'endpoint') { this.advance(); endpoint = this.parseString(); }
2092
+ else { this.advance(); }
2093
+ this.skipNewlines();
2094
+ }
2095
+ if (this.at(T.DEDENT)) this.advance();
2096
+ return new ASTNode('PushDecl', { publicKey, privateKey, endpoint });
2097
+ }
2098
+
2099
+ // search "meilisearch" "http://localhost:7700" apiKey:
2100
+ // index "products"
2101
+ parseSearch() {
2102
+ this.expect(T.SEARCH);
2103
+ const engine = this.parseString();
2104
+ const host = this.parseExpression();
2105
+ const apiKey = this.parseExpression();
2106
+ this.expect(T.COLON);
2107
+ this.skipNewlines();
2108
+ this.expect(T.INDENT);
2109
+ let index = null;
2110
+ while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
2111
+ this.skipNewlines();
2112
+ if (this.at(T.DEDENT) || this.at(T.EOF)) break;
2113
+ const kw = this.peek().value;
2114
+ if (kw === 'index') { this.advance(); index = this.parseString(); }
2115
+ else { this.advance(); }
2116
+ this.skipNewlines();
2117
+ }
2118
+ if (this.at(T.DEDENT)) this.advance();
2119
+ return new ASTNode('SearchDecl', { engine, host, apiKey, index });
2120
+ }
2121
+
2122
+ // image "input.jpg" -> "output.jpg":
2123
+ // resize 800 600
2124
+ // crop 100 100 400 300
2125
+ // watermark "logo.png"
2126
+ parseImage() {
2127
+ this.expect(T.IMAGE);
2128
+ const input = this.parseExpression();
2129
+ let output = null;
2130
+ if (this.at(T.ARROW)) { this.advance(); output = this.parseExpression(); }
2131
+ this.expect(T.COLON);
2132
+ this.skipNewlines();
2133
+ this.expect(T.INDENT);
2134
+ const operations = [];
2135
+ while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
2136
+ this.skipNewlines();
2137
+ if (this.at(T.DEDENT) || this.at(T.EOF)) break;
2138
+ const op = this.advance().value;
2139
+ const args = [];
2140
+ while (!this.at(T.NEWLINE) && !this.at(T.DEDENT) && !this.at(T.EOF)) {
2141
+ if (this.at(T.STRING)) { args.push(this.parseString()); }
2142
+ else if (this.at(T.NUMBER)) { args.push(this.advance().value); }
2143
+ else { args.push(this.parseExpression()); break; }
2144
+ }
2145
+ operations.push({ op, args });
2146
+ this.skipNewlines();
2147
+ }
2148
+ if (this.at(T.DEDENT)) this.advance();
2149
+ return new ASTNode('ImageDecl', { input, output, operations });
2150
+ }
2071
2151
  }
package/src/tokens.js CHANGED
@@ -98,6 +98,9 @@ export const T = {
98
98
  STORAGE: 'STORAGE',
99
99
  PDF: 'PDF',
100
100
  I18N: 'I18N',
101
+ PUSH: 'PUSH',
102
+ SEARCH: 'SEARCH',
103
+ IMAGE: 'IMAGE',
101
104
 
102
105
  // Operators
103
106
  ASSIGN: 'ASSIGN',
@@ -224,6 +227,9 @@ export const KEYWORDS = {
224
227
  'storage': T.STORAGE,
225
228
  'pdf': T.PDF,
226
229
  'i18n': T.I18N,
230
+ 'push': T.PUSH,
231
+ 'search': T.SEARCH,
232
+ 'image': T.IMAGE,
227
233
  'true': T.BOOL,
228
234
  'false': T.BOOL,
229
235
  'null': T.NULL,
@@ -87,7 +87,7 @@
87
87
  "name": "keyword.control.naide"
88
88
  },
89
89
  "keywords-server": {
90
- "match": "\\b(server|bot|slash|get|post|put|del|patch|mid|cors|limit|auth|crud|static|ws|sse|group|upload|cookie|session|view|cache|validate|openapi|error|on|page|cli|mail|graphql|desktop|screen|oauth|pay|storage|pdf|i18n)\\b",
90
+ "match": "\\b(server|bot|slash|get|post|put|del|patch|mid|cors|limit|auth|crud|static|ws|sse|group|upload|cookie|session|view|cache|validate|openapi|error|on|page|cli|mail|graphql|desktop|screen|oauth|pay|storage|pdf|i18n|push|search|image)\\b",
91
91
  "name": "keyword.other.naide"
92
92
  },
93
93
  "keywords-declaration": {