egonlog 0.0.1

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 ADDED
@@ -0,0 +1,222 @@
1
+ # Typestache: static typing for mustache
2
+
3
+ > Get your templates to "if it compiles, it probably works!"
4
+
5
+ Typestache is still a work in progress. Use with caution.
6
+
7
+ ## Quickstart
8
+ Install Typestache:
9
+
10
+ ```bash
11
+ npm install typestache
12
+ ```
13
+
14
+ Typestache consists of a CLI tool and a library. To use it, point the CLI tool towards your template directory:
15
+
16
+ ```bash
17
+ typestache src/templates
18
+ ```
19
+
20
+ Typestache will find your mustache files, and create a corresponding TypeScript file:
21
+
22
+ ```bash
23
+ src/templates
24
+ - myTemplate.mustache
25
+ - myTemplate.ts
26
+ ```
27
+
28
+ Now simply import this TypeScript file and render it.
29
+
30
+ ```typescript
31
+ import myTemplate from './myTemplate';
32
+
33
+ const data = {
34
+ name: 'Adit',
35
+ value: 10000,
36
+ in_ca: true
37
+ };
38
+
39
+ const result = myTemplate(data);
40
+ ```
41
+
42
+ Easy as that! Behind the scenes, Typestache has converted your mustache template into a typed template for you, so if you have a type error, TypeScript will tell you. Example:
43
+
44
+ ```typescript
45
+ const data = {
46
+ name: 'Adit',
47
+ value: 10000,
48
+ in_ca: "true" // Oops, this should be a boolean
49
+ };
50
+
51
+ const result = myTemplate(data); // Error: Type 'string' is not assignable to type 'boolean'.
52
+ ```
53
+
54
+ [See examples here](https://github.com/egonSchiele/typestache/tree/main/examples).
55
+
56
+ Typestache also extends mustache syntax to add type hints. Here's a short example:
57
+
58
+ ```mustache
59
+ I am {{age:number}} years old.
60
+ ```
61
+
62
+ Now `age` will be a `number` in the generated TypeScript file.
63
+
64
+ **Heads up, typestache is *not* a drop-in replacement for mustache.** Read more below.
65
+
66
+ ## Deriving types
67
+
68
+ Typestache will automatically derive types for you. For example, given this template
69
+
70
+ ```mustache
71
+ {{#person}}
72
+ Hello, {{name}}!
73
+ {{/person}}
74
+ ```
75
+
76
+ Typestache will derive this type:
77
+
78
+ ```typescript
79
+ type TemplateType = {
80
+ person: boolean;
81
+ name: string | boolean | number;
82
+ };
83
+ ```
84
+
85
+ ## Specifying types
86
+ If you know what type something will be, you can tell typestache. For example, in the above example, we know `name` is a `string`. Here's how we can tell typestache:
87
+
88
+ ```mustache
89
+ {{#person}}
90
+ Hello, {{name:string}}!
91
+ {{/person}}
92
+ ```
93
+
94
+ and here's the derived type:
95
+
96
+ ```typescript
97
+ type TemplateType = {
98
+ person: boolean;
99
+ name: string;
100
+ };
101
+ ```
102
+
103
+ Here is another example. `amount` can be a `string` or a `number`, so we have used a union here.
104
+
105
+ ```mustache
106
+ {{#person}}
107
+ Hello, {{name:string}}! You have {{amount:string|number}} in your account.
108
+ {{/person}}
109
+ ```
110
+
111
+ and here's the derived type:
112
+
113
+ ```typescript
114
+ type TemplateType = {
115
+ person: boolean;
116
+ name: string;
117
+ amount: string | number;
118
+ };
119
+ ```
120
+
121
+ ### Sections and scoping
122
+
123
+ In all these examples, you'll notice `name` is never a key. `person` is always a `boolean`, it's never an object with a key `name`. Mustache has very loose scoping rules. Deriving a type for this template
124
+
125
+ ```mustache
126
+ {{#person}}
127
+ Hello, {{name}}!
128
+ {{/person}}
129
+ ```
130
+
131
+ in mustache might look something like this:
132
+
133
+ ```typescript
134
+ type TemplateType = {
135
+ person: boolean;
136
+ name: string | boolean | number;
137
+ } | {
138
+ person: {
139
+ name: string | boolean | number;
140
+ }
141
+ } | {
142
+ person: {
143
+ name: string | boolean | number;
144
+ }
145
+ }[]
146
+ ```
147
+
148
+ Even that's not enough, since technically, `person` could be any truthy value, and `person` and `name` could both be `undefined`.
149
+
150
+ A type like this is harder to read, and reduces type safety. Things look even worse as you have more sections, and more variables. So typestache chooses to interpret every variable as if it's in the global context. If you want `name` to be a key on `person`, use the new `this` keyword:
151
+
152
+ ```mustache
153
+ {{#person}}
154
+ Hello, {{this.name}}!
155
+ {{/person}}
156
+ ```
157
+
158
+ Generates this type:
159
+
160
+ ```typescript
161
+ type TemplateType = {
162
+ person: {
163
+ name: string | boolean | number;
164
+ }
165
+ }
166
+ ```
167
+
168
+ You'll also notice `person` is an object. If you want it to be an array of objects, use `[]` after the name in the opening tag:
169
+
170
+ ```mustache
171
+ {{#person[]}}
172
+ Hello, {{this.name}}!
173
+ {{/person}}
174
+ ```
175
+
176
+
177
+ Generates this type:
178
+
179
+ ```typescript
180
+ type TemplateType = {
181
+ person: {
182
+ name: string | boolean | number;
183
+ }[];
184
+ }
185
+ ```
186
+
187
+ ### Optionals
188
+
189
+ Finally, typestache makes all variables required by default. You can make something optional by adding a question mark at the end of the name, like this:
190
+
191
+ ```mustache
192
+ Hello, {{name?:string}}!
193
+ ```
194
+
195
+ Generates this type:
196
+
197
+ ```typescript
198
+ type TemplateType = {
199
+ name?: string;
200
+ }
201
+ ```
202
+
203
+ ## Typestache doesn't implement the entire mustache spec.
204
+
205
+ There are several parts of the mustache spec that Typestache does not implement. The most important one to know about is that Typestache handles scope differently. Mustache is very loose with its scoping, which makes it hard to write a useful type for it.
206
+
207
+ Here are some other things not currently supported:
208
+
209
+ Eventual support:
210
+
211
+ - Nested sections
212
+ - Lambdas (no support for dynamic templates)
213
+ - partials
214
+
215
+ No support planned:
216
+
217
+ - Dynamic names
218
+ - blocks
219
+ - parents
220
+ - custom delimiter tags.
221
+
222
+ For the ones where there is no support planned, mostly it's because the feature would be very hard or impossible to type correctly. The nature of dynamic partials, for example, means we don't know what will be generated until runtime, which makes it impossible to type.
@@ -0,0 +1,19 @@
1
+ export type LogLevel = "error" | "warn" | "info" | "debug";
2
+ export declare class Logger {
3
+ private level;
4
+ private timers;
5
+ constructor(level: LogLevel);
6
+ private shouldLog;
7
+ private log;
8
+ error(...args: unknown[]): void;
9
+ warn(...args: unknown[]): void;
10
+ info(...args: unknown[]): void;
11
+ debug(...args: unknown[]): void;
12
+ table(...args: unknown[]): void;
13
+ highlight(...args: unknown[]): void;
14
+ setLevel(level: LogLevel): void;
15
+ getLevel(): LogLevel;
16
+ startTimer(label: string): void;
17
+ endTimer(label: string): void;
18
+ time<T>(label: string, fn: () => Promise<T>): Promise<T>;
19
+ }
package/dist/index.js ADDED
@@ -0,0 +1,91 @@
1
+ import { color } from "termcolors";
2
+ const LOG_LEVELS = {
3
+ error: 0,
4
+ warn: 1,
5
+ info: 2,
6
+ debug: 3,
7
+ };
8
+ export class Logger {
9
+ level;
10
+ timers = {};
11
+ constructor(level) {
12
+ this.level = level;
13
+ }
14
+ shouldLog(messageLevel) {
15
+ return LOG_LEVELS[messageLevel] <= LOG_LEVELS[this.level];
16
+ }
17
+ log(level, ...args) {
18
+ if (!this.shouldLog(level)) {
19
+ return;
20
+ }
21
+ const timestamp = new Date().toISOString();
22
+ const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
23
+ switch (level) {
24
+ case "error":
25
+ console.error(color.red(prefix, ...args));
26
+ break;
27
+ case "warn":
28
+ console.warn(color.yellow(prefix, ...args));
29
+ break;
30
+ case "info":
31
+ console.info(color.green(prefix, ...args));
32
+ break;
33
+ case "debug":
34
+ console.debug(prefix, ...args);
35
+ break;
36
+ }
37
+ }
38
+ error(...args) {
39
+ this.log("error", ...args);
40
+ }
41
+ warn(...args) {
42
+ this.log("warn", ...args);
43
+ }
44
+ info(...args) {
45
+ this.log("info", ...args);
46
+ }
47
+ debug(...args) {
48
+ this.log("debug", ...args);
49
+ }
50
+ table(...args) {
51
+ if (!this.shouldLog("debug")) {
52
+ return;
53
+ }
54
+ console.table(...args);
55
+ }
56
+ highlight(...args) {
57
+ if (!this.shouldLog("debug")) {
58
+ return;
59
+ }
60
+ const highlighted = args.map((arg) => typeof arg === "string" ? color.bgWhite.black(arg) : arg);
61
+ console.log(...highlighted);
62
+ }
63
+ setLevel(level) {
64
+ this.level = level;
65
+ }
66
+ getLevel() {
67
+ return this.level;
68
+ }
69
+ startTimer(label) {
70
+ this.timers[label] = performance.now();
71
+ }
72
+ endTimer(label) {
73
+ if (this.timers[label]) {
74
+ const duration = performance.now() - this.timers[label];
75
+ this.info(`Timer [${label}]: ${duration.toFixed(2)} ms`);
76
+ delete this.timers[label];
77
+ }
78
+ else {
79
+ this.warn(`No timer found for label: ${label}`);
80
+ }
81
+ }
82
+ async time(label, fn) {
83
+ this.startTimer(label);
84
+ try {
85
+ return await fn();
86
+ }
87
+ finally {
88
+ this.endTimer(label);
89
+ }
90
+ }
91
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "egonlog",
3
+ "version": "0.0.1",
4
+ "description": "Simple logging library",
5
+ "homepage": "https://github.com/egonSchiele/egonlog",
6
+ "scripts": {
7
+ "test": "vitest",
8
+ "test:tsc": "tsc -p tests/tsconfig.json",
9
+ "coverage": "vitest --coverage",
10
+ "build": "rm -rf dist && tsc",
11
+ "start": "cd dist && node index.js"
12
+ },
13
+ "files": [
14
+ "./dist"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.js",
20
+ "types": "./dist/index.d.ts"
21
+ }
22
+ },
23
+ "type": "module",
24
+ "types": "./dist/index.d.ts",
25
+ "keywords": [
26
+ "egonlog",
27
+ "logging",
28
+ "library"
29
+ ],
30
+ "author": "Aditya Bhargava",
31
+ "license": "ISC",
32
+ "devDependencies": {
33
+ "@types/node": "^25.0.3",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^4.0.16"
36
+ },
37
+ "dependencies": {
38
+ "termcolors": "github:egonSchiele/termcolors"
39
+ }
40
+ }