princejs 1.9.1 → 1.9.3
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 +97 -5
- package/dist/client.d.ts +95 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +99 -0
- package/dist/prince.d.ts +51 -1
- package/dist/prince.d.ts.map +1 -1
- package/dist/prince.js +12801 -11
- package/dist/scheduler.d.ts +83 -6
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +54 -1
- package/package.json +6 -2
package/Readme.md
CHANGED
|
@@ -53,18 +53,18 @@ app
|
|
|
53
53
|
})));
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
###
|
|
56
|
+
### Middleware
|
|
57
57
|
|
|
58
58
|
* CORS
|
|
59
59
|
* Logger
|
|
60
60
|
* Rate Limiting
|
|
61
61
|
* Static Files
|
|
62
62
|
|
|
63
|
-
###
|
|
63
|
+
### Validation (Zod)
|
|
64
64
|
|
|
65
|
-
###
|
|
65
|
+
### File Uploads
|
|
66
66
|
|
|
67
|
-
###
|
|
67
|
+
### Response Builder
|
|
68
68
|
|
|
69
69
|
### WebSocket Support
|
|
70
70
|
|
|
@@ -105,8 +105,89 @@ const app = prince();
|
|
|
105
105
|
app.plugin(usersPlugin, { prefix: "/api" });
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
### OpenAPI + Scalar Docs
|
|
109
|
+
|
|
110
|
+
Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com) API reference UI — all from a single `app.openapi()` call. Routes, validation, and docs stay in sync automatically.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { prince } from "princejs";
|
|
114
|
+
import { z } from "zod";
|
|
115
|
+
|
|
116
|
+
const app = prince();
|
|
117
|
+
|
|
118
|
+
const api = app.openapi({ title: "My API", version: "1.0.0" }, "/docs", { theme: "moon" });
|
|
119
|
+
|
|
120
|
+
api.route("GET", "/users/:id", {
|
|
121
|
+
summary: "Get user by ID",
|
|
122
|
+
tags: ["users"],
|
|
123
|
+
schema: {
|
|
124
|
+
response: z.object({ id: z.string(), name: z.string() }),
|
|
125
|
+
},
|
|
126
|
+
}, (req) => ({ id: req.params!.id, name: "Alice" }));
|
|
127
|
+
|
|
128
|
+
api.route("POST", "/users", {
|
|
129
|
+
summary: "Create user",
|
|
130
|
+
tags: ["users"],
|
|
131
|
+
schema: {
|
|
132
|
+
body: z.object({ name: z.string().min(2), email: z.string().email() }),
|
|
133
|
+
response: z.object({ id: z.string(), name: z.string(), email: z.string() }),
|
|
134
|
+
},
|
|
135
|
+
}, (req) => ({ id: crypto.randomUUID(), ...req.parsedBody }));
|
|
136
|
+
|
|
137
|
+
app.listen(3000);
|
|
138
|
+
// → GET /docs Scalar UI
|
|
139
|
+
// → GET /docs.json Raw OpenAPI JSON
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**`api.route()` does three things at once:**
|
|
143
|
+
- Registers the route on PrinceJS (same as `app.get()` / `app.post()`)
|
|
144
|
+
- Auto-wires `validate(schema.body)` middleware — no separate import needed
|
|
145
|
+
- Writes the full OpenAPI spec entry including path params, request body, query params, and response schema
|
|
146
|
+
|
|
147
|
+
| `schema` key | Runtime effect | Scalar docs |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `body` | ✅ Validates & strips via `validate()` | ✅ requestBody model |
|
|
150
|
+
| `query` | ❌ docs only | ✅ typed query params |
|
|
151
|
+
| `response` | ❌ docs only | ✅ 200 response model |
|
|
152
|
+
|
|
153
|
+
Routes registered with `app.get()` / `app.post()` directly never appear in the docs — useful for internal health checks, webhooks, and admin endpoints.
|
|
154
|
+
|
|
155
|
+
**Available themes:** `default` · `moon` · `purple` · `solarized` · `bluePlanet` · `deepSpace` · `saturn` · `kepler` · `mars`
|
|
156
|
+
|
|
108
157
|
### Database (SQLite)
|
|
109
158
|
|
|
159
|
+
### End to End Type-Safety
|
|
160
|
+
|
|
161
|
+
PrinceJS supports contract-based type safety to sync your frontend and backend seamlessly. By defining an API contract, your client receives full TypeScript autocompletion and type-checking for routes, parameters, and responses.
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
**Define Your Contract**
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
type ApiContract = {
|
|
168
|
+
"GET /users/:id": {
|
|
169
|
+
params: { id: string };
|
|
170
|
+
response: { id: string; name: string };
|
|
171
|
+
};
|
|
172
|
+
"POST /users": {
|
|
173
|
+
body: { name: string };
|
|
174
|
+
response: { id: string; ok: boolean };
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Initialize The Client**
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { createClient } from "princejs/client";
|
|
183
|
+
|
|
184
|
+
const client = createClient<ApiContract>("http://localhost:3000");
|
|
185
|
+
|
|
186
|
+
// Fully typed request and response
|
|
187
|
+
const user = await client.get("/users/:id", { params: { id: "42" } });
|
|
188
|
+
console.log(user.name); // Typed as string
|
|
189
|
+
```
|
|
190
|
+
|
|
110
191
|
---
|
|
111
192
|
|
|
112
193
|
## Deploy (Vercel, Workers, Deno)
|
|
@@ -171,7 +252,7 @@ import { prince } from "princejs";
|
|
|
171
252
|
import { cors, logger, rateLimit, auth, apiKey, jwt, session, compress, serve } from "princejs/middleware";
|
|
172
253
|
import { validate } from "princejs/validation";
|
|
173
254
|
import { cache, upload, sse } from "princejs/helpers";
|
|
174
|
-
import { cron } from "princejs/scheduler";
|
|
255
|
+
import { cron, openapi } from "princejs/scheduler";
|
|
175
256
|
import { Html, Head, Body, H1, P, render } from "princejs/jsx"
|
|
176
257
|
import { db } from "princejs/db";
|
|
177
258
|
import { z } from "zod";
|
|
@@ -253,6 +334,17 @@ app.get("/users", () => users.query("SELECT * FROM users"));
|
|
|
253
334
|
|
|
254
335
|
cron("*/1 * * * *", () => console.log("PrinceJS heartbeat"));
|
|
255
336
|
|
|
337
|
+
// OpenAPI + Scalar
|
|
338
|
+
const api = app.openapi({ title: "PrinceJS App", version: "1.0.0" }, "/docs");
|
|
339
|
+
api.route("GET", "/items", {
|
|
340
|
+
summary: "List items",
|
|
341
|
+
tags: ["items"],
|
|
342
|
+
schema: {
|
|
343
|
+
query: z.object({ q: z.string().optional() }),
|
|
344
|
+
response: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
345
|
+
},
|
|
346
|
+
}, () => [{ id: "1", name: "Widget" }]);
|
|
347
|
+
|
|
256
348
|
app.listen(3000);
|
|
257
349
|
```
|
|
258
350
|
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* princejs/client - End-to-end type-safe API client
|
|
3
|
+
*
|
|
4
|
+
* Define an API contract shared between server and client. The client infers
|
|
5
|
+
* request/response types from the contract—no code generation required.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // shared/api.ts - Define contract (shared or imported by both server & client)
|
|
9
|
+
* export type ApiContract = {
|
|
10
|
+
* "GET /health": { response: { ok: boolean } };
|
|
11
|
+
* "GET /users/:id": { params: { id: string }; response: { id: string; name: string } };
|
|
12
|
+
* "POST /users": { body: { name: string }; response: { id: string; name: string } };
|
|
13
|
+
* };
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // client.ts
|
|
17
|
+
* import { createClient } from "princejs/client";
|
|
18
|
+
* import type { ApiContract } from "./shared/api";
|
|
19
|
+
*
|
|
20
|
+
* const client = createClient<ApiContract>("http://localhost:3000");
|
|
21
|
+
*
|
|
22
|
+
* const health = await client.get("/health"); // { ok: boolean }
|
|
23
|
+
* const user = await client.get("/users/:id", { params: { id: "1" } });
|
|
24
|
+
* const created = await client.post("/users", { body: { name: "Alice" } });
|
|
25
|
+
*/
|
|
26
|
+
/** A single route in the API contract. Key format: "METHOD /path/:param" */
|
|
27
|
+
export interface RouteDef {
|
|
28
|
+
/** Path params (e.g. { id: string } for /users/:id) */
|
|
29
|
+
params?: Record<string, string>;
|
|
30
|
+
/** Query params shape (optional, for type hints) */
|
|
31
|
+
query?: Record<string, string>;
|
|
32
|
+
/** Request body for POST/PUT/PATCH */
|
|
33
|
+
body?: unknown;
|
|
34
|
+
/** Response type (default: unknown) */
|
|
35
|
+
response?: unknown;
|
|
36
|
+
}
|
|
37
|
+
/** API contract: maps "METHOD /path" to route definition */
|
|
38
|
+
export type PrinceApiContract = Record<string, RouteDef>;
|
|
39
|
+
type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
40
|
+
/** Extract route keys for a method */
|
|
41
|
+
type KeysForMethod<C extends PrinceApiContract, M extends Method> = Extract<keyof C, `${M} ${string}`>;
|
|
42
|
+
/** Path part from key "GET /users/:id" -> "/users/:id" */
|
|
43
|
+
type PathFromKey<K extends string> = K extends `${Method} ${infer P}` ? P : never;
|
|
44
|
+
/** Route def for a key */
|
|
45
|
+
type DefFor<C extends PrinceApiContract, K extends keyof C> = C[K] extends RouteDef ? C[K] : never;
|
|
46
|
+
/** Response type from route def */
|
|
47
|
+
type ResponseType<D> = D extends {
|
|
48
|
+
response?: infer R;
|
|
49
|
+
} ? R : unknown;
|
|
50
|
+
/** Params type from route def */
|
|
51
|
+
type ParamsType<D> = D extends {
|
|
52
|
+
params?: infer P;
|
|
53
|
+
} ? P : Record<string, never>;
|
|
54
|
+
/** Body type from route def */
|
|
55
|
+
type BodyType<D> = D extends {
|
|
56
|
+
body?: infer B;
|
|
57
|
+
} ? B : undefined;
|
|
58
|
+
/** Options for GET/DELETE (params, query) */
|
|
59
|
+
type GetOpts<D> = ParamsType<D> extends Record<string, never> ? {
|
|
60
|
+
params?: never;
|
|
61
|
+
query?: Record<string, string>;
|
|
62
|
+
} : {
|
|
63
|
+
params: ParamsType<D>;
|
|
64
|
+
query?: Record<string, string>;
|
|
65
|
+
};
|
|
66
|
+
/** Options for POST/PUT/PATCH */
|
|
67
|
+
type MutateOpts<D> = ParamsType<D> extends Record<string, never> ? {
|
|
68
|
+
body: BodyType<D>;
|
|
69
|
+
query?: Record<string, string>;
|
|
70
|
+
} : {
|
|
71
|
+
params: ParamsType<D>;
|
|
72
|
+
body: BodyType<D>;
|
|
73
|
+
query?: Record<string, string>;
|
|
74
|
+
};
|
|
75
|
+
export interface ClientOptions {
|
|
76
|
+
/** Base fetch init (headers, credentials, etc.) */
|
|
77
|
+
init?: RequestInit;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Create an end-to-end type-safe API client from a contract.
|
|
81
|
+
* Share the contract type between server and client for full type safety.
|
|
82
|
+
*/
|
|
83
|
+
export declare function createClient<C extends PrinceApiContract>(baseUrl: string, options?: ClientOptions): PrinceClient<C>;
|
|
84
|
+
/** Typed client interface */
|
|
85
|
+
export interface PrinceClient<C extends PrinceApiContract> {
|
|
86
|
+
get<K extends KeysForMethod<C, "GET">>(path: PathFromKey<K>, opts?: GetOpts<DefFor<C, K>>): Promise<ResponseType<DefFor<C, K>>>;
|
|
87
|
+
post<K extends KeysForMethod<C, "POST">>(path: PathFromKey<K>, opts: MutateOpts<DefFor<C, K>>): Promise<ResponseType<DefFor<C, K>>>;
|
|
88
|
+
put<K extends KeysForMethod<C, "PUT">>(path: PathFromKey<K>, opts: MutateOpts<DefFor<C, K>>): Promise<ResponseType<DefFor<C, K>>>;
|
|
89
|
+
patch<K extends KeysForMethod<C, "PATCH">>(path: PathFromKey<K>, opts: MutateOpts<DefFor<C, K>>): Promise<ResponseType<DefFor<C, K>>>;
|
|
90
|
+
delete<K extends KeysForMethod<C, "DELETE">>(path: PathFromKey<K>, opts?: GetOpts<DefFor<C, K>>): Promise<ResponseType<DefFor<C, K>>>;
|
|
91
|
+
/** Raw fetch (untyped) for custom requests */
|
|
92
|
+
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
93
|
+
}
|
|
94
|
+
export {};
|
|
95
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
function buildPath(pattern, params) {
|
|
3
|
+
if (!params)
|
|
4
|
+
return pattern;
|
|
5
|
+
let out = pattern;
|
|
6
|
+
for (const [k, v] of Object.entries(params)) {
|
|
7
|
+
out = out.replace(`:${k}`, encodeURIComponent(v));
|
|
8
|
+
}
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
11
|
+
function buildUrl(base, path, query) {
|
|
12
|
+
const url = new URL(path, base);
|
|
13
|
+
if (query) {
|
|
14
|
+
for (const [k, v] of Object.entries(query)) {
|
|
15
|
+
url.searchParams.set(k, v);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return url.toString();
|
|
19
|
+
}
|
|
20
|
+
async function fetchJson(url, init) {
|
|
21
|
+
const res = await fetch(url, {
|
|
22
|
+
...init,
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
...init?.headers
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
const text = await res.text();
|
|
30
|
+
throw new Error(`API error ${res.status}: ${text}`);
|
|
31
|
+
}
|
|
32
|
+
return res.json();
|
|
33
|
+
}
|
|
34
|
+
function createClient(baseUrl, options) {
|
|
35
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
36
|
+
const init = options?.init ?? {};
|
|
37
|
+
const get = async (path, opts) => {
|
|
38
|
+
const def = {};
|
|
39
|
+
const params = opts && "params" in opts ? opts.params : undefined;
|
|
40
|
+
const query = opts?.query;
|
|
41
|
+
const builtPath = buildPath(path, params);
|
|
42
|
+
const url = buildUrl(base, builtPath, query);
|
|
43
|
+
return fetchJson(url, init);
|
|
44
|
+
};
|
|
45
|
+
const post = async (path, opts) => {
|
|
46
|
+
const params = opts && "params" in opts ? opts.params : undefined;
|
|
47
|
+
const body = opts && "body" in opts ? opts.body : undefined;
|
|
48
|
+
const query = opts?.query;
|
|
49
|
+
const builtPath = buildPath(path, params);
|
|
50
|
+
const url = buildUrl(base, builtPath, query);
|
|
51
|
+
return fetchJson(url, {
|
|
52
|
+
...init,
|
|
53
|
+
method: "POST",
|
|
54
|
+
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const put = async (path, opts) => {
|
|
58
|
+
const params = opts && "params" in opts ? opts.params : undefined;
|
|
59
|
+
const body = opts && "body" in opts ? opts.body : undefined;
|
|
60
|
+
const query = opts?.query;
|
|
61
|
+
const builtPath = buildPath(path, params);
|
|
62
|
+
const url = buildUrl(base, builtPath, query);
|
|
63
|
+
return fetchJson(url, {
|
|
64
|
+
...init,
|
|
65
|
+
method: "PUT",
|
|
66
|
+
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const patch = async (path, opts) => {
|
|
70
|
+
const params = opts && "params" in opts ? opts.params : undefined;
|
|
71
|
+
const body = opts && "body" in opts ? opts.body : undefined;
|
|
72
|
+
const query = opts?.query;
|
|
73
|
+
const builtPath = buildPath(path, params);
|
|
74
|
+
const url = buildUrl(base, builtPath, query);
|
|
75
|
+
return fetchJson(url, {
|
|
76
|
+
...init,
|
|
77
|
+
method: "PATCH",
|
|
78
|
+
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
const del = async (path, opts) => {
|
|
82
|
+
const params = opts && opts.params ? opts.params : undefined;
|
|
83
|
+
const query = opts?.query;
|
|
84
|
+
const builtPath = buildPath(path, params);
|
|
85
|
+
const url = buildUrl(base, builtPath, query);
|
|
86
|
+
return fetchJson(url, { ...init, method: "DELETE" });
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
get,
|
|
90
|
+
post,
|
|
91
|
+
put,
|
|
92
|
+
patch,
|
|
93
|
+
delete: del,
|
|
94
|
+
fetch: (path, reqInit) => globalThis.fetch(`${base}${path}`, { ...init, ...reqInit })
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export {
|
|
98
|
+
createClient
|
|
99
|
+
};
|
package/dist/prince.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { OpenAPIBuilder, ScalarOptions } from "./scheduler";
|
|
2
|
+
import { z } from "zod";
|
|
1
3
|
type Next = () => Promise<Response>;
|
|
2
4
|
type Middleware = (req: PrinceRequest, next: Next) => Promise<Response | undefined> | Response | undefined;
|
|
3
5
|
type HandlerResult = Response | Record<string, any> | string | Uint8Array;
|
|
@@ -32,13 +34,30 @@ declare class ResponseBuilder {
|
|
|
32
34
|
redirect(url: string, status?: number): Response;
|
|
33
35
|
build(): Response;
|
|
34
36
|
}
|
|
37
|
+
export interface RouteSchema {
|
|
38
|
+
/** Zod schema for the request body — auto-wires validate() middleware */
|
|
39
|
+
body?: z.ZodTypeAny;
|
|
40
|
+
/** Zod schema for query parameters — written into the OpenAPI spec */
|
|
41
|
+
query?: z.ZodObject<any>;
|
|
42
|
+
/** Zod schema for the response body — written into the OpenAPI spec */
|
|
43
|
+
response?: z.ZodTypeAny;
|
|
44
|
+
}
|
|
45
|
+
export interface RouteOperation {
|
|
46
|
+
summary?: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
tags?: string[];
|
|
49
|
+
/** Attach Zod schemas to auto-generate request/response models in Scalar */
|
|
50
|
+
schema?: RouteSchema;
|
|
51
|
+
responses?: Record<string, unknown>;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
}
|
|
35
54
|
export declare class Prince {
|
|
36
55
|
private devMode;
|
|
37
56
|
private rawRoutes;
|
|
38
57
|
private middlewares;
|
|
39
58
|
private errorHandler?;
|
|
40
59
|
private wsRoutes;
|
|
41
|
-
private
|
|
60
|
+
private openapiSpec;
|
|
42
61
|
private router;
|
|
43
62
|
private staticRoutes;
|
|
44
63
|
private staticMiddlewares;
|
|
@@ -77,6 +96,37 @@ export declare class Prince {
|
|
|
77
96
|
private executeHandler;
|
|
78
97
|
handleFetch(req: Request): Promise<Response>;
|
|
79
98
|
fetch(req: Request): Promise<Response>;
|
|
99
|
+
/**
|
|
100
|
+
* Mounts an OpenAPI spec + Scalar UI onto this Prince app.
|
|
101
|
+
*
|
|
102
|
+
* Calling this method returns an `OpenAPIBuilder` whose `.route()` method
|
|
103
|
+
* registers a route on the Prince app **and** writes the matching path entry
|
|
104
|
+
* into the OpenAPI spec simultaneously — so the two can never drift.
|
|
105
|
+
*
|
|
106
|
+
* Two routes are auto-registered:
|
|
107
|
+
* GET `docsPath` → Scalar UI (e.g. /docs)
|
|
108
|
+
* GET `docsPath`.json → Raw spec (e.g. /docs.json)
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* const app = prince();
|
|
112
|
+
* const api = app.openapi({ title: "My API", version: "1.0.0" }, "/docs");
|
|
113
|
+
*
|
|
114
|
+
* // Registers GET /hello on Prince AND adds it to the OpenAPI spec
|
|
115
|
+
* api.route("GET", "/hello", {
|
|
116
|
+
* summary: "Say hello",
|
|
117
|
+
* responses: { 200: { description: "OK" } },
|
|
118
|
+
* }, (req) => ({ message: "hello" }));
|
|
119
|
+
*
|
|
120
|
+
* app.listen(3000);
|
|
121
|
+
* // → GET /docs → Scalar UI
|
|
122
|
+
* // → GET /docs.json → raw OpenAPI JSON
|
|
123
|
+
*/
|
|
124
|
+
openapi(info: {
|
|
125
|
+
title: string;
|
|
126
|
+
version: string;
|
|
127
|
+
}, docsPath?: string, scalarOptions?: ScalarOptions): OpenAPIBuilder & {
|
|
128
|
+
route: (method: string, path: string, operation: Record<string, unknown>, ...args: (RouteHandler | Middleware)[]) => OpenAPIBuilder;
|
|
129
|
+
};
|
|
80
130
|
listen(port?: number): void;
|
|
81
131
|
}
|
|
82
132
|
export declare const prince: (dev?: boolean) => Prince;
|
package/dist/prince.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prince.d.ts","sourceRoot":"","sources":["../src/prince.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prince.d.ts","sourceRoot":"","sources":["../src/prince.ts"],"names":[],"mappings":"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"}
|