blockend-cli 1.1.0 → 1.3.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/dist/index.js +77 -34
- package/package.json +36 -35
- package/dist/index.mjs +0 -49
- package/dist/templates/auth/index.hbs +0 -13
- package/dist/templates/auth/meta.json +0 -12
- package/dist/templates/rate-limiter/index.hbs +0 -38
- package/dist/templates/rate-limiter/meta.json +0 -22
package/dist/index.js
CHANGED
|
@@ -142,7 +142,7 @@ async function initCommand() {
|
|
|
142
142
|
const cwd = process.cwd();
|
|
143
143
|
const configPath = join6(cwd, "blockend.json");
|
|
144
144
|
console.log(`
|
|
145
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
145
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
146
146
|
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
147
147
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
148
148
|
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
@@ -197,18 +197,6 @@ async function initCommand() {
|
|
|
197
197
|
outro(format.muted("Initialization cancelled."));
|
|
198
198
|
return;
|
|
199
199
|
}
|
|
200
|
-
const language = await select({
|
|
201
|
-
message: "Confirm primary language",
|
|
202
|
-
initialValue: context.language === "typescript" ? "typescript" : "javascript",
|
|
203
|
-
options: [
|
|
204
|
-
{ value: "typescript", label: "TypeScript" },
|
|
205
|
-
{ value: "javascript", label: "JavaScript" }
|
|
206
|
-
]
|
|
207
|
-
});
|
|
208
|
-
if (isCancel(language)) {
|
|
209
|
-
outro(format.muted("Initialization cancelled."));
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
200
|
const availableAliases = Object.keys(context.aliasMap || {});
|
|
213
201
|
const baseAliasToken = availableAliases.length > 0 ? availableAliases[0] : "@/";
|
|
214
202
|
const blockAliasInput = await text({
|
|
@@ -241,7 +229,7 @@ async function initCommand() {
|
|
|
241
229
|
const configPayload = {
|
|
242
230
|
$schema: "https://blockend.dev/schema.json",
|
|
243
231
|
environment: framework,
|
|
244
|
-
language,
|
|
232
|
+
language: "typescript",
|
|
245
233
|
includeRedis,
|
|
246
234
|
aliases: {
|
|
247
235
|
blocks: blockAlias
|
|
@@ -262,7 +250,7 @@ async function initCommand() {
|
|
|
262
250
|
}
|
|
263
251
|
|
|
264
252
|
// src/commands/add.ts
|
|
265
|
-
import path2, { join as join7 } from "path";
|
|
253
|
+
import path2, { join as join7, dirname } from "path";
|
|
266
254
|
import fs2 from "fs/promises";
|
|
267
255
|
import { exec } from "child_process";
|
|
268
256
|
import { intro as intro2, outro as outro2, select as select2, spinner as spinner2, confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
@@ -270,25 +258,55 @@ import pc2 from "picocolors";
|
|
|
270
258
|
var REPO_OWNER = "codewithnuh";
|
|
271
259
|
var REPO_NAME = "blockend";
|
|
272
260
|
var BRANCH = "master";
|
|
273
|
-
var LOCAL_DEV_URL = "http://localhost:5000";
|
|
274
|
-
var MANIFEST_URL = `${LOCAL_DEV_URL}/registry/index.json`;
|
|
275
261
|
var RAW_CDN_BASE = `https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${BRANCH}`;
|
|
262
|
+
var MANIFEST_URL = `${RAW_CDN_BASE}/registry/index.json`;
|
|
276
263
|
function handleCancel(value) {
|
|
277
264
|
if (isCancel2(value)) {
|
|
278
265
|
outro2(pc2.yellow("\u26A0 Operation cancelled. Exiting Blockend CLI cleanly."));
|
|
279
266
|
process.exit(0);
|
|
280
267
|
}
|
|
281
268
|
}
|
|
269
|
+
async function findUp(filename, startDir) {
|
|
270
|
+
let dir = startDir;
|
|
271
|
+
while (true) {
|
|
272
|
+
const checkPath = join7(dir, filename);
|
|
273
|
+
try {
|
|
274
|
+
await fs2.access(checkPath);
|
|
275
|
+
return checkPath;
|
|
276
|
+
} catch {
|
|
277
|
+
const parent = dirname(dir);
|
|
278
|
+
if (parent === dir) break;
|
|
279
|
+
dir = parent;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
282
284
|
async function addCommand(blockName) {
|
|
283
285
|
intro2(pc2.bgBlack(pc2.magenta(" Blockend Component Ingestion ")));
|
|
284
286
|
const cwd = process.cwd();
|
|
285
|
-
const configPath =
|
|
287
|
+
const configPath = await findUp("blockend.json", cwd);
|
|
288
|
+
if (!configPath) {
|
|
289
|
+
outro2(pc2.red("\u2716 blockend.json not found. Run 'npx blockend init' first."));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const rootDir = dirname(configPath);
|
|
286
293
|
let config;
|
|
287
294
|
try {
|
|
288
295
|
const configFile = await fs2.readFile(configPath, "utf-8");
|
|
289
296
|
config = JSON.parse(configFile);
|
|
290
297
|
} catch {
|
|
291
|
-
outro2(pc2.red("\u2716
|
|
298
|
+
outro2(pc2.red("\u2716 Failed to parse blockend.json layout configuration."));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (config.language !== "typescript") {
|
|
302
|
+
outro2(
|
|
303
|
+
pc2.yellow(
|
|
304
|
+
`
|
|
305
|
+
\u2139 Blockend forces modern architectural standards.
|
|
306
|
+
To ensure strict type safety and absolute code ownership, the registry exclusively supports TypeScript.
|
|
307
|
+
Please migrate your project configuration to TypeScript to ingest blocks.`
|
|
308
|
+
)
|
|
309
|
+
);
|
|
292
310
|
return;
|
|
293
311
|
}
|
|
294
312
|
const s = spinner2();
|
|
@@ -303,14 +321,19 @@ async function addCommand(blockName) {
|
|
|
303
321
|
s.stop(pc2.red("\u2716 Failed to fetch the component registry from GitHub network paths."));
|
|
304
322
|
return;
|
|
305
323
|
}
|
|
324
|
+
const envKey = String(config.environment);
|
|
306
325
|
let targetBlock = blockName;
|
|
307
326
|
if (!targetBlock) {
|
|
308
|
-
const availableOptions = Object.keys(registry).map((key) => ({
|
|
327
|
+
const availableOptions = Object.keys(registry).filter((key) => registry[key].environments[envKey] !== void 0).map((key) => ({
|
|
309
328
|
value: key,
|
|
310
329
|
label: `${key} - ${pc2.dim(registry[key].description)}`
|
|
311
330
|
}));
|
|
312
331
|
if (availableOptions.length === 0) {
|
|
313
|
-
outro2(
|
|
332
|
+
outro2(
|
|
333
|
+
pc2.yellow(
|
|
334
|
+
`\u26A0 No backend blocks are currently available for your framework layer: [${envKey}].`
|
|
335
|
+
)
|
|
336
|
+
);
|
|
314
337
|
return;
|
|
315
338
|
}
|
|
316
339
|
const selectBlockPrompt = await select2({
|
|
@@ -325,7 +348,6 @@ async function addCommand(blockName) {
|
|
|
325
348
|
outro2(pc2.red(`\u2716 Block "${targetBlock}" does not exist in the remote registry.`));
|
|
326
349
|
return;
|
|
327
350
|
}
|
|
328
|
-
const envKey = String(config.environment);
|
|
329
351
|
const envConfig = blockMeta.environments[envKey];
|
|
330
352
|
if (!envConfig) {
|
|
331
353
|
outro2(
|
|
@@ -352,13 +374,17 @@ async function addCommand(blockName) {
|
|
|
352
374
|
}
|
|
353
375
|
variantMeta = envConfig.variants[selectedVariant];
|
|
354
376
|
}
|
|
377
|
+
const packageJsonPath = await findUp("package.json", rootDir);
|
|
378
|
+
if (!packageJsonPath) {
|
|
379
|
+
outro2(pc2.red("\u2716 Could not locate package.json in your current directory layout hierarchy."));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const packageJsonDir = dirname(packageJsonPath);
|
|
355
383
|
let packageJson;
|
|
356
384
|
try {
|
|
357
|
-
packageJson = JSON.parse(
|
|
358
|
-
await fs2.readFile(join7(cwd, "package.json"), "utf-8")
|
|
359
|
-
);
|
|
385
|
+
packageJson = JSON.parse(await fs2.readFile(packageJsonPath, "utf-8"));
|
|
360
386
|
} catch {
|
|
361
|
-
outro2(pc2.red("\u2716
|
|
387
|
+
outro2(pc2.red("\u2716 Failed parsing file data configurations from target package.json location."));
|
|
362
388
|
return;
|
|
363
389
|
}
|
|
364
390
|
const installedDeps = {
|
|
@@ -378,14 +404,14 @@ async function addCommand(blockName) {
|
|
|
378
404
|
});
|
|
379
405
|
handleCancel(shouldInstallPrompt);
|
|
380
406
|
if (shouldInstallPrompt) {
|
|
381
|
-
const packageManager = await fs2.access(join7(
|
|
407
|
+
const packageManager = await fs2.access(join7(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
|
|
382
408
|
s.start(`Preparing native workspace via ${packageManager}...`);
|
|
383
409
|
try {
|
|
384
410
|
const installCmd = packageManager === "pnpm" ? `pnpm add ${missingDeps.join(" ")}` : `npm install ${missingDeps.join(" ")}`;
|
|
385
411
|
s.stop(pc2.cyan(`Executing: ${installCmd}
|
|
386
412
|
`));
|
|
387
413
|
await new Promise((resolve, reject) => {
|
|
388
|
-
const child = exec(installCmd, { cwd });
|
|
414
|
+
const child = exec(installCmd, { cwd: packageJsonDir });
|
|
389
415
|
child.stdout?.on("data", (data) => {
|
|
390
416
|
process.stdout.write(pc2.dim(data));
|
|
391
417
|
});
|
|
@@ -407,7 +433,7 @@ async function addCommand(blockName) {
|
|
|
407
433
|
}
|
|
408
434
|
s.start(`Downloading clean production template block [${targetBlock}]...`);
|
|
409
435
|
try {
|
|
410
|
-
const BASE_URL =
|
|
436
|
+
const BASE_URL = RAW_CDN_BASE;
|
|
411
437
|
const coreFileUrl = `${BASE_URL}/${envConfig.core}`;
|
|
412
438
|
const coreFetchResponse = await fetch(coreFileUrl);
|
|
413
439
|
if (!coreFetchResponse.ok) {
|
|
@@ -418,14 +444,29 @@ async function addCommand(blockName) {
|
|
|
418
444
|
if (physicalPath.startsWith("@")) {
|
|
419
445
|
physicalPath = "./src/blocks";
|
|
420
446
|
}
|
|
421
|
-
let targetFolder = path2.resolve(
|
|
447
|
+
let targetFolder = path2.resolve(rootDir, physicalPath);
|
|
422
448
|
if (variantMeta && selectedVariant) {
|
|
423
449
|
targetFolder = join7(targetFolder, targetBlock);
|
|
424
450
|
}
|
|
425
451
|
await fs2.mkdir(targetFolder, { recursive: true });
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
|
|
452
|
+
const coreOutputFilename = `${targetBlock}.ts`;
|
|
453
|
+
const coreTargetFileLocation = join7(targetFolder, coreOutputFilename);
|
|
454
|
+
try {
|
|
455
|
+
await fs2.access(coreTargetFileLocation);
|
|
456
|
+
s.stop();
|
|
457
|
+
const overwritePrompt = await confirm2({
|
|
458
|
+
message: `\u26A0 Block component file [${coreOutputFilename}] already exists. Overwrite custom code revisions?`,
|
|
459
|
+
initialValue: false
|
|
460
|
+
});
|
|
461
|
+
handleCancel(overwritePrompt);
|
|
462
|
+
if (!overwritePrompt) {
|
|
463
|
+
outro2(pc2.yellow("\u2139 Operation aborted safely. Local code modifications preserved."));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
s.start(`Re-downloading template block [${targetBlock}]...`);
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
await fs2.writeFile(coreTargetFileLocation, coreCodeTemplate, "utf-8");
|
|
429
470
|
if (variantMeta && selectedVariant) {
|
|
430
471
|
const variantFileUrl = `${BASE_URL}/${variantMeta.path}`;
|
|
431
472
|
const variantFetchResponse = await fetch(variantFileUrl);
|
|
@@ -435,7 +476,7 @@ async function addCommand(blockName) {
|
|
|
435
476
|
);
|
|
436
477
|
}
|
|
437
478
|
const variantCodeTemplate = await variantFetchResponse.text();
|
|
438
|
-
const variantOutputFilename = `store-${selectedVariant}
|
|
479
|
+
const variantOutputFilename = `store-${selectedVariant}.ts`;
|
|
439
480
|
await fs2.writeFile(join7(targetFolder, variantOutputFilename), variantCodeTemplate, "utf-8");
|
|
440
481
|
}
|
|
441
482
|
const cleanDisplayPath = variantMeta && selectedVariant ? `${physicalPath.replace(/\\/g, "/")}/${targetBlock}` : physicalPath.replace(/\\/g, "/");
|
|
@@ -445,9 +486,11 @@ async function addCommand(blockName) {
|
|
|
445
486
|
`\u2728 Source blocks written to ${cleanDisplayPath}/ layout. Code ownership transferred!`
|
|
446
487
|
)
|
|
447
488
|
);
|
|
489
|
+
process.exit(0);
|
|
448
490
|
} catch (error) {
|
|
449
491
|
s.stop(pc2.red("\u2716 Fatal crash occurred while downloading or transferring file layouts."));
|
|
450
492
|
console.error(error);
|
|
493
|
+
process.exit(1);
|
|
451
494
|
}
|
|
452
495
|
}
|
|
453
496
|
|
package/package.json
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "blockend-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Intelligent, modular backend blocks right inside your terminal",
|
|
5
|
-
"main": "./dist/index.js",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"blockend": "./dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"readme.md"
|
|
13
|
-
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "tsup src/index.ts --format esm --dts --out-dir dist"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "blockend-cli",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Intelligent, modular backend blocks right inside your terminal",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"blockend": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"readme.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup src/index.ts --format esm --dts --out-dir dist",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"backend",
|
|
21
|
+
"express",
|
|
22
|
+
"nextjs",
|
|
23
|
+
"architecture"
|
|
24
|
+
],
|
|
25
|
+
"author": "codewithnuh",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"packageManager": "pnpm@10.33.2",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@clack/prompts": "^1.5.1",
|
|
30
|
+
"commander": "^15.0.0",
|
|
31
|
+
"picocolors": "^1.1.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^25.9.3"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/index.mjs
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/index.ts
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
import { intro, outro, select, text } from "@clack/prompts";
|
|
6
|
-
import pc from "picocolors";
|
|
7
|
-
var program = new Command();
|
|
8
|
-
program.name("blockend").description("Zero-dependency production backend blocks CLI").version("0.1.0");
|
|
9
|
-
program.command("init").description("Initialize blockend configuration in your project").action(async () => {
|
|
10
|
-
intro(pc.bgBlack(pc.cyan(" Blockend Initialization ")));
|
|
11
|
-
const environment = await select({
|
|
12
|
-
message: "Select your backend framework environment:",
|
|
13
|
-
options: [
|
|
14
|
-
{ value: "express", label: "Express.js" },
|
|
15
|
-
{ value: "fastify", label: "Fastify" },
|
|
16
|
-
{ value: "nextjs", label: "Next.js (App Router)" }
|
|
17
|
-
]
|
|
18
|
-
});
|
|
19
|
-
const blockPath = await text({
|
|
20
|
-
message: "Where should we save your backend blocks?",
|
|
21
|
-
initialValue: "./src/blocks",
|
|
22
|
-
placeholder: "./src/blocks"
|
|
23
|
-
});
|
|
24
|
-
console.log("\nWriting configuration target data...");
|
|
25
|
-
console.log(
|
|
26
|
-
pc.green(`\u2714 Targets set: Environment -> ${String(environment)}, Path -> ${String(blockPath)}`)
|
|
27
|
-
);
|
|
28
|
-
outro(pc.cyan("blockend.json configured mock-successfully."));
|
|
29
|
-
});
|
|
30
|
-
program.command("add [block]").description("Add a backend block to your project").action(async (block) => {
|
|
31
|
-
intro(pc.bgBlack(pc.magenta(" Blockend Add Tool ")));
|
|
32
|
-
if (!block) {
|
|
33
|
-
const selectedBlock = await select({
|
|
34
|
-
message: "Which backend block would you like to add?",
|
|
35
|
-
options: [
|
|
36
|
-
{ value: "rate-limiter", label: "rate-limiter (Redis/Memory)" },
|
|
37
|
-
{ value: "auth-handler", label: "auth-handler (JWT/Session)" },
|
|
38
|
-
{ value: "error-handler", label: "global-error-handler" }
|
|
39
|
-
]
|
|
40
|
-
});
|
|
41
|
-
block = selectedBlock;
|
|
42
|
-
}
|
|
43
|
-
console.log(pc.yellow(`
|
|
44
|
-
[Mock Run]: Fetching metadata manifest for "${block}"...`));
|
|
45
|
-
console.log(pc.green(`\u2714 [Mock Run]: Injected missing dependencies into your package context.`));
|
|
46
|
-
console.log(pc.green(`\u2714 [Mock Run]: Written file cleanly into your target blocks path.`));
|
|
47
|
-
outro(pc.magenta(`Successfully added ${block} block!`));
|
|
48
|
-
});
|
|
49
|
-
program.parse(process.argv);
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export interface SessionUser {
|
|
2
|
-
id: string;
|
|
3
|
-
email: string;
|
|
4
|
-
roles: string[];
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function requireRole(user: SessionUser | null, role: string): SessionUser {
|
|
8
|
-
if (!user || !user.roles.includes(role)) {
|
|
9
|
-
throw new Error("Unauthorized");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return user;
|
|
13
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export interface RateLimitOptions {
|
|
2
|
-
limit: number;
|
|
3
|
-
windowMs: number;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface RateLimitResult {
|
|
7
|
-
allowed: boolean;
|
|
8
|
-
remaining: number;
|
|
9
|
-
resetAt: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const buckets = new Map<string, { count: number; resetAt: number }>();
|
|
13
|
-
|
|
14
|
-
export function createRateLimiter(options: RateLimitOptions) {
|
|
15
|
-
return function checkRateLimit(key: string): RateLimitResult {
|
|
16
|
-
const now = Date.now();
|
|
17
|
-
const current = buckets.get(key);
|
|
18
|
-
|
|
19
|
-
if (!current || current.resetAt <= now) {
|
|
20
|
-
const resetAt = now + options.windowMs;
|
|
21
|
-
buckets.set(key, { count: 1, resetAt });
|
|
22
|
-
return { allowed: true, remaining: options.limit - 1, resetAt };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (current.count >= options.limit) {
|
|
26
|
-
return { allowed: false, remaining: 0, resetAt: current.resetAt };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
current.count += 1;
|
|
30
|
-
return {
|
|
31
|
-
allowed: true,
|
|
32
|
-
remaining: Math.max(options.limit - current.count, 0),
|
|
33
|
-
resetAt: current.resetAt
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const rateLimitDriver = "{{driver}}";
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "rate-limiter",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"dependencies": [],
|
|
5
|
-
"prompts": [
|
|
6
|
-
{
|
|
7
|
-
"id": "driver",
|
|
8
|
-
"type": "select",
|
|
9
|
-
"message": "Select your storage backend:",
|
|
10
|
-
"options": [
|
|
11
|
-
{ "value": "in-memory", "label": "In-Memory (Zero dependencies)" },
|
|
12
|
-
{ "value": "redis", "label": "Redis (Scalable cluster)" }
|
|
13
|
-
]
|
|
14
|
-
}
|
|
15
|
-
],
|
|
16
|
-
"files": [
|
|
17
|
-
{
|
|
18
|
-
"template": "index.hbs",
|
|
19
|
-
"output": "src/lib/blocks/{{name}}.ts"
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|