@scalar/snippetz 0.9.26 → 0.9.28

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/clients/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AA2CpD;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,EA+H3B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/clients/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AA4CpD;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,EAqI3B,CAAA"}
@@ -15,6 +15,7 @@ import { jsFetch } from '../plugins/js/fetch/index.js';
15
15
  import { jsJquery } from '../plugins/js/jquery/index.js';
16
16
  import { jsOfetch } from '../plugins/js/ofetch/index.js';
17
17
  import { jsXhr } from '../plugins/js/xhr/index.js';
18
+ import { juliaHttp } from '../plugins/julia/http/index.js';
18
19
  import { kotlinOkhttp } from '../plugins/kotlin/okhttp/index.js';
19
20
  import { nodeAxios } from '../plugins/node/axios/index.js';
20
21
  import { nodeFetch } from '../plugins/node/fetch/index.js';
@@ -96,6 +97,12 @@ export const clients = [
96
97
  default: 'fetch',
97
98
  clients: [jsFetch, jsAxios, jsOfetch, jsJquery, jsXhr],
98
99
  },
100
+ {
101
+ key: 'julia',
102
+ title: 'Julia',
103
+ default: 'http',
104
+ clients: [juliaHttp],
105
+ },
99
106
  {
100
107
  key: 'kotlin',
101
108
  title: 'Kotlin',
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Wraps a string in double quotes, escaped and ready to be inserted into Julia source code.
3
+ */
4
+ export declare const wrapInDoubleQuotes: (str: string) => string;
5
+ /**
6
+ * Produces an indented string using 4 spaces per level, following the Julia style guide.
7
+ */
8
+ export declare const indent: (level: number, text: string) => string;
9
+ /**
10
+ * Renders a collection (a vector, a `Dict`, a `HTTP.Form`, …) from already formatted entries.
11
+ *
12
+ * Short collections stay on a single line, everything else gets one entry per
13
+ * line so that generated snippets remain readable.
14
+ *
15
+ * @param entries - The formatted entries, without trailing commas
16
+ * @param open - The opening token, for example `[` or `Dict(`
17
+ * @param close - The closing token, for example `]` or `)`
18
+ * @param level - The indentation level the collection itself sits at
19
+ */
20
+ export declare const formatCollection: (entries: string[], open: string, close: string, level: number) => string;
21
+ /**
22
+ * Formats `name => value` pairs as a Julia vector of pairs, which is what HTTP.jl
23
+ * expects for headers and query parameters.
24
+ */
25
+ export declare const formatPairVector: (pairs: {
26
+ name: string;
27
+ value: string;
28
+ }[], level: number) => string;
29
+ /**
30
+ * Formats `name => value` pairs as a Julia `Dict`.
31
+ */
32
+ export declare const formatDict: (pairs: {
33
+ name: string;
34
+ value: string;
35
+ }[], level: number) => string;
36
+ /**
37
+ * Converts a parsed JSON value into the equivalent Julia literal.
38
+ *
39
+ * Objects become `Dict`s and arrays become vectors, so the snippet can be edited
40
+ * with plain Julia syntax before it is serialized with `JSON.json`.
41
+ */
42
+ export declare const formatValue: (value: unknown, level: number) => string;
43
+ //# sourceMappingURL=julia.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"julia.d.ts","sourceRoot":"","sources":["../../src/libs/julia.ts"],"names":[],"mappings":"AAeA;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,KAAG,MAAkC,CAAA;AAEnF;;GAEG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,EAAE,MAAM,MAAM,KAAG,MAA2C,CAAA;AAEhG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,EAAE,EAAE,MAAM,MAAM,EAAE,OAAO,MAAM,EAAE,OAAO,MAAM,KAAG,MAchG,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,OAAO,MAAM,KAAG,MAMxF,CAAA;AAEH;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,OAAO,MAAM,KAAG,MAMlF,CAAA;AAEH;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,EAAE,OAAO,MAAM,KAAG,MAsC3D,CAAA"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Escapes a string for use in a Julia double-quoted string literal.
3
+ *
4
+ * Julia interpolates `$` inside string literals, so it has to be escaped as
5
+ * well, otherwise `"$foo"` would be evaluated instead of sent literally.
6
+ */
7
+ const escapeString = (str) => str
8
+ .replace(/\\/g, '\\\\')
9
+ .replace(/"/g, '\\"')
10
+ .replace(/\$/g, '\\$')
11
+ .replace(/\n/g, '\\n')
12
+ .replace(/\r/g, '\\r')
13
+ .replace(/\t/g, '\\t');
14
+ /**
15
+ * Wraps a string in double quotes, escaped and ready to be inserted into Julia source code.
16
+ */
17
+ export const wrapInDoubleQuotes = (str) => `"${escapeString(str)}"`;
18
+ /**
19
+ * Produces an indented string using 4 spaces per level, following the Julia style guide.
20
+ */
21
+ export const indent = (level, text) => `${' '.repeat(level * 4)}${text}`;
22
+ /**
23
+ * Renders a collection (a vector, a `Dict`, a `HTTP.Form`, …) from already formatted entries.
24
+ *
25
+ * Short collections stay on a single line, everything else gets one entry per
26
+ * line so that generated snippets remain readable.
27
+ *
28
+ * @param entries - The formatted entries, without trailing commas
29
+ * @param open - The opening token, for example `[` or `Dict(`
30
+ * @param close - The closing token, for example `]` or `)`
31
+ * @param level - The indentation level the collection itself sits at
32
+ */
33
+ export const formatCollection = (entries, open, close, level) => {
34
+ if (entries.length === 0) {
35
+ return `${open}${close}`;
36
+ }
37
+ const [first] = entries;
38
+ if (entries.length === 1 && first && !first.includes('\n')) {
39
+ return `${open}${first}${close}`;
40
+ }
41
+ const lines = entries.map((entry) => indent(level + 1, entry)).join(',\n');
42
+ return `${open}\n${lines}\n${indent(level, close)}`;
43
+ };
44
+ /**
45
+ * Formats `name => value` pairs as a Julia vector of pairs, which is what HTTP.jl
46
+ * expects for headers and query parameters.
47
+ */
48
+ export const formatPairVector = (pairs, level) => formatCollection(pairs.map(({ name, value }) => `${wrapInDoubleQuotes(name)} => ${wrapInDoubleQuotes(value)}`), '[', ']', level);
49
+ /**
50
+ * Formats `name => value` pairs as a Julia `Dict`.
51
+ */
52
+ export const formatDict = (pairs, level) => formatCollection(pairs.map(({ name, value }) => `${wrapInDoubleQuotes(name)} => ${wrapInDoubleQuotes(value)}`), 'Dict(', ')', level);
53
+ /**
54
+ * Converts a parsed JSON value into the equivalent Julia literal.
55
+ *
56
+ * Objects become `Dict`s and arrays become vectors, so the snippet can be edited
57
+ * with plain Julia syntax before it is serialized with `JSON.json`.
58
+ */
59
+ export const formatValue = (value, level) => {
60
+ if (value === null || value === undefined) {
61
+ return 'nothing';
62
+ }
63
+ if (typeof value === 'boolean') {
64
+ return value ? 'true' : 'false';
65
+ }
66
+ if (typeof value === 'number') {
67
+ return Number.isFinite(value) ? String(value) : 'nothing';
68
+ }
69
+ if (typeof value === 'string') {
70
+ return wrapInDoubleQuotes(value);
71
+ }
72
+ if (Array.isArray(value)) {
73
+ return formatCollection(value.map((item) => formatValue(item, level + 1)), '[', ']', level);
74
+ }
75
+ if (typeof value === 'object') {
76
+ return formatCollection(Object.entries(value).map(([key, item]) => `${wrapInDoubleQuotes(key)} => ${formatValue(item, level + 1)}`), 'Dict(', ')', level);
77
+ }
78
+ return wrapInDoubleQuotes(String(value));
79
+ };
@@ -0,0 +1,6 @@
1
+ import type { Plugin } from '@scalar/types/snippetz';
2
+ /**
3
+ * julia/http
4
+ */
5
+ export declare const juliaHttp: Plugin;
6
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../../src/plugins/julia/http/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,MAAM,EAAuB,MAAM,wBAAwB,CAAA;AAsIrF;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,MAgFvB,CAAA"}
@@ -0,0 +1,154 @@
1
+ import { normalizeMethod } from '../../../libs/http.js';
2
+ import { formatCollection, formatDict, formatPairVector, formatValue, indent, wrapInDoubleQuotes } from '../../../libs/julia.js';
3
+ /** Verbs HTTP.jl exposes as `HTTP.<verb>(url, headers, body)` helpers. */
4
+ const VERBS_WITH_BODY = ['POST', 'PUT', 'PATCH', 'DELETE', 'QUERY'];
5
+ /** Verbs HTTP.jl exposes as `HTTP.<verb>(url, headers)` helpers, without a body argument. */
6
+ const VERBS_WITHOUT_BODY = ['GET', 'HEAD', 'OPTIONS'];
7
+ /** The indentation level of the arguments inside a multi-line request call. */
8
+ const ARGUMENT_LEVEL = 1;
9
+ /**
10
+ * Builds the Julia expression for a multipart part, using `HTTP.Multipart` for
11
+ * file uploads and a plain string for everything else.
12
+ */
13
+ const formatMultipartPart = (param) => {
14
+ const name = wrapInDoubleQuotes(param.name);
15
+ if (param.fileName === undefined) {
16
+ return `${name} => ${wrapInDoubleQuotes(param.value ?? '')}`;
17
+ }
18
+ const file = wrapInDoubleQuotes(param.fileName);
19
+ const args = [file, `open(${file})`];
20
+ if (param.contentType) {
21
+ args.push(wrapInDoubleQuotes(param.contentType));
22
+ }
23
+ return `${name} => HTTP.Multipart(${args.join(', ')})`;
24
+ };
25
+ /**
26
+ * Turns the request body into a Julia expression.
27
+ *
28
+ * JSON payloads are rendered as `Dict`s so they can be edited without touching
29
+ * the raw JSON, form bodies use the native types HTTP.jl knows how to encode
30
+ * (`Dict` for url-encoded, `HTTP.Form` for multipart) and anything else is sent
31
+ * as a plain string.
32
+ */
33
+ const getBody = (postData) => {
34
+ if (!postData) {
35
+ return undefined;
36
+ }
37
+ const { mimeType, text, params } = postData;
38
+ if (mimeType === 'application/json' && text) {
39
+ try {
40
+ return {
41
+ value: `JSON.json(${formatValue(JSON.parse(text), ARGUMENT_LEVEL)})`,
42
+ packages: ['JSON'],
43
+ omitContentType: false,
44
+ };
45
+ }
46
+ catch {
47
+ // Not valid JSON, fall through and send the raw text
48
+ }
49
+ }
50
+ if (mimeType === 'multipart/form-data' && params?.length) {
51
+ return {
52
+ value: formatCollection(params.map(formatMultipartPart), 'HTTP.Form([', '])', ARGUMENT_LEVEL),
53
+ packages: [],
54
+ omitContentType: true,
55
+ };
56
+ }
57
+ if (mimeType === 'application/x-www-form-urlencoded' && params?.length) {
58
+ return {
59
+ value: formatDict(params.map((param) => ({ name: param.name, value: param.value ?? '' })), ARGUMENT_LEVEL),
60
+ packages: [],
61
+ omitContentType: false,
62
+ };
63
+ }
64
+ if (!text) {
65
+ return undefined;
66
+ }
67
+ return { value: wrapInDoubleQuotes(text), packages: [], omitContentType: false };
68
+ };
69
+ /**
70
+ * Assembles the request call, keeping it on a single line when the URL is the
71
+ * only argument and spreading it over multiple lines otherwise.
72
+ */
73
+ const buildCall = (callee, required, optional, keywords) => {
74
+ const positional = [...required, ...optional];
75
+ if (optional.length === 0 && keywords.length === 0) {
76
+ return [`response = ${callee}(${positional.join(', ')})`];
77
+ }
78
+ const lines = [`response = ${callee}(`];
79
+ positional.forEach((argument, index) => {
80
+ const isLast = index === positional.length - 1;
81
+ const separator = isLast ? (keywords.length ? ';' : '') : ',';
82
+ lines.push(`${indent(ARGUMENT_LEVEL, argument)}${separator}`);
83
+ });
84
+ keywords.forEach((keyword, index) => {
85
+ lines.push(`${indent(ARGUMENT_LEVEL, keyword)}${index === keywords.length - 1 ? '' : ','}`);
86
+ });
87
+ lines.push(')');
88
+ return lines;
89
+ };
90
+ /**
91
+ * julia/http
92
+ */
93
+ export const juliaHttp = {
94
+ target: 'julia',
95
+ client: 'http',
96
+ title: 'HTTP.jl',
97
+ generate(request, configuration) {
98
+ const normalizedRequest = {
99
+ url: 'https://example.com',
100
+ ...request,
101
+ };
102
+ const method = normalizeMethod(normalizedRequest.method);
103
+ const body = getBody(normalizedRequest.postData);
104
+ // Deduplicate headers, dropping the content type when HTTP.jl generates it
105
+ const headers = new Map();
106
+ for (const header of normalizedRequest.headers ?? []) {
107
+ if (!header.name || (body?.omitContentType && header.name.toLowerCase() === 'content-type')) {
108
+ continue;
109
+ }
110
+ headers.set(header.name, header.value ?? '');
111
+ }
112
+ const verb = [...VERBS_WITH_BODY, ...VERBS_WITHOUT_BODY].includes(method) ? method.toLowerCase() : undefined;
113
+ const callee = verb ? `HTTP.${verb}` : 'HTTP.request';
114
+ const url = wrapInDoubleQuotes(normalizedRequest.url);
115
+ const required = verb ? [url] : [wrapInDoubleQuotes(method), url];
116
+ const optional = [];
117
+ const keywords = [];
118
+ if (headers.size) {
119
+ optional.push(formatPairVector([...headers].map(([name, value]) => ({ name, value })), ARGUMENT_LEVEL));
120
+ }
121
+ if (body) {
122
+ // The first positional argument after the URL is always the headers, both
123
+ // for the verb helpers (`HTTP.post(url, headers, body)`) and for
124
+ // `HTTP.request(method, url, headers, body)`. A body passed positionally
125
+ // without headers in front of it would therefore be read as the headers
126
+ // argument, so it has to go through the `body =` keyword instead.
127
+ // `HTTP.get`, `HTTP.head` and `HTTP.options` never take a positional body.
128
+ if (VERBS_WITHOUT_BODY.includes(method) || headers.size === 0) {
129
+ keywords.push(`body = ${body.value}`);
130
+ }
131
+ else {
132
+ optional.push(body.value);
133
+ }
134
+ }
135
+ if (normalizedRequest.queryString?.length) {
136
+ keywords.push(`query = ${formatPairVector(normalizedRequest.queryString, ARGUMENT_LEVEL)}`);
137
+ }
138
+ if (normalizedRequest.cookies?.length) {
139
+ keywords.push(`cookies = ${formatDict(normalizedRequest.cookies, ARGUMENT_LEVEL)}`);
140
+ }
141
+ if (configuration?.auth?.username && configuration?.auth?.password) {
142
+ const { username, password } = configuration.auth;
143
+ keywords.push(`basicauth = (${wrapInDoubleQuotes(username)}, ${wrapInDoubleQuotes(password)})`);
144
+ }
145
+ const packages = ['HTTP', ...(body?.packages ?? [])];
146
+ return [
147
+ ...packages.map((name) => `using ${name}`),
148
+ '',
149
+ ...buildCall(callee, required, optional, keywords),
150
+ '',
151
+ 'println(String(response.body))',
152
+ ].join('\n');
153
+ },
154
+ };
@@ -0,0 +1,2 @@
1
+ export { juliaHttp } from './http.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/plugins/julia/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA"}
@@ -0,0 +1 @@
1
+ export { juliaHttp } from './http.js';
@@ -6,7 +6,7 @@ export declare function snippetz(): {
6
6
  print<T extends TargetId>(target: T, client: ClientId<T>, request: Partial<HarRequest>): string | undefined;
7
7
  clients(): import("@scalar/types/snippetz").Target[];
8
8
  plugins(): {
9
- target: "c" | "clojure" | "csharp" | "dart" | "fsharp" | "go" | "http" | "java" | "js" | "kotlin" | "node" | "objc" | "ocaml" | "php" | "powershell" | "python" | "r" | "ruby" | "rust" | "shell" | "swift";
9
+ target: "c" | "clojure" | "csharp" | "dart" | "fsharp" | "go" | "http" | "java" | "js" | "julia" | "kotlin" | "node" | "objc" | "ocaml" | "php" | "powershell" | "python" | "r" | "ruby" | "rust" | "shell" | "swift";
10
10
  client: "http" | "libcurl" | "clj_http" | "httpclient" | "restsharp" | "native" | "http1.1" | "asynchttp" | "nethttp" | "okhttp" | "unirest" | "axios" | "fetch" | "jquery" | "ofetch" | "xhr" | "undici" | "nsurlsession" | "cohttp" | "curl" | "guzzle" | "laravel" | "restmethod" | "webrequest" | "python3" | "requests" | "aiohttp" | "httpx_sync" | "httpx_async" | "httr2" | "reqwest" | "httpie" | "wget";
11
11
  }[];
12
12
  findPlugin: <T extends TargetId>(target: T | string, client: ClientId<T> | string) => import("@scalar/types/snippetz").Plugin | undefined;
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "url": "git+https://github.com/scalar/scalar.git",
10
10
  "directory": "packages/snippetz"
11
11
  },
12
- "version": "0.9.26",
12
+ "version": "0.9.28",
13
13
  "engines": {
14
14
  "node": ">=22"
15
15
  },
@@ -112,6 +112,11 @@
112
112
  "types": "./dist/plugins/js/xhr/index.d.ts",
113
113
  "default": "./dist/plugins/js/xhr/index.js"
114
114
  },
115
+ "./plugins/julia/http": {
116
+ "import": "./dist/plugins/julia/http/index.js",
117
+ "types": "./dist/plugins/julia/http/index.d.ts",
118
+ "default": "./dist/plugins/julia/http/index.js"
119
+ },
115
120
  "./plugins/kotlin/okhttp": {
116
121
  "import": "./dist/plugins/kotlin/okhttp/index.js",
117
122
  "types": "./dist/plugins/kotlin/okhttp/index.d.ts",
@@ -238,10 +243,10 @@
238
243
  "CHANGELOG"
239
244
  ],
240
245
  "dependencies": {
241
- "js-base64": "^3.7.8",
246
+ "js-base64": "^3.9.2",
242
247
  "stringify-object": "^6.0.0",
243
- "@scalar/helpers": "0.10.0",
244
- "@scalar/types": "0.18.0"
248
+ "@scalar/helpers": "0.11.1",
249
+ "@scalar/types": "0.18.2"
245
250
  },
246
251
  "devDependencies": {
247
252
  "@types/stringify-object": "^4.0.5",