diff --git a/docs/api/04-standard-library/fs/api-reference.md b/docs/api/04-standard-library/fs/api-reference.md
index 04ae46446af..08cc8b22b77 100644
--- a/docs/api/04-standard-library/fs/api-reference.md
+++ b/docs/api/04-standard-library/fs/api-reference.md
@@ -51,6 +51,7 @@ new fs.Util();
| metadata
| Gets the stats of the given path. |
| mkdir
| Create a directory. |
| mkdtemp
| Create a temporary directory. |
+| readBytes
| Read the contents of the file as bytes. |
| readdir
| Read the contents of the directory. |
| readFile
| Read the entire contents of a file. |
| readJson
| Read the contents of the file and convert it to JSON. |
@@ -60,10 +61,12 @@ new fs.Util();
| setPermissions
| Set the permissions of the file, directory, etc. |
| symlink
| Creates a symbolic link. |
| symlinkMetadata
| Gets the stats of the given path without following symbolic links. |
+| tryReadBytes
| If the file exists, read the contents of the file as bytes; |
| tryReaddir
| If the path exists, read the contents of the directory; |
| tryReadFile
| If the file exists and can be read successfully, read the entire contents; |
| tryReadJson
| Retrieve the contents of the file and convert it to JSON if the file exists and can be parsed successfully, otherwise, return `undefined`. |
| tryReadYaml
| Convert all YAML objects from a single file into JSON objects if the file exists and can be parsed successfully, `undefined` otherwise. |
+| writeBytes
| Write bytes to a file, replacing the file if it already exists. |
| writeFile
| Writes data to a file, replacing the file if it already exists. |
| writeJson
| Writes JSON to a file, replacing the file if it already exists. |
| writeYaml
| Writes multiple YAML objects to a file, replacing the file if it already exists. |
@@ -356,6 +359,24 @@ The prefix for the directory to be created, default `wingtemp`.
---
+##### `readBytes`
+
+```wing
+bring fs;
+
+fs.readBytes(filepath: str);
+```
+
+Read the contents of the file as bytes.
+
+###### `filepath`Required
+
+- *Type:* str
+
+The file path.
+
+---
+
##### `readdir`
```wing
@@ -572,6 +593,26 @@ The path to get stats for.
---
+##### `tryReadBytes`
+
+```wing
+bring fs;
+
+fs.tryReadBytes(filepath: str);
+```
+
+If the file exists, read the contents of the file as bytes;
+
+otherwise, return `undefined`.
+
+###### `filepath`Required
+
+- *Type:* str
+
+The file path.
+
+---
+
##### `tryReaddir`
```wing
@@ -656,6 +697,43 @@ The file path of the YAML file.
---
+##### `writeBytes`
+
+```wing
+bring fs;
+
+fs.writeBytes(filepath: str, data: Bytes, options?: WriteFileOptions);
+```
+
+Write bytes to a file, replacing the file if it already exists.
+
+###### `filepath`Required
+
+- *Type:* str
+
+The file path that needs to be written.
+
+---
+
+###### `data`Required
+
+- *Type:* Bytes
+
+The bytes to write.
+
+---
+
+###### `options`Optional
+
+- *Type:* WriteFileOptions
+
+The `encoding` can be set to specify the character encoding.
+
+And the `flag` can be set to specify the attributes.
+If a flag is not provided, it defaults to `"w"`.
+
+---
+
##### `writeFile`
```wing
diff --git a/docs/api/04-standard-library/std/bytes.md b/docs/api/04-standard-library/std/bytes.md
new file mode 100644
index 00000000000..f583428b2f2
--- /dev/null
+++ b/docs/api/04-standard-library/std/bytes.md
@@ -0,0 +1,282 @@
+---
+title: bytes
+id: bytes
+---
+
+# API Reference
+
+
+## Classes
+
+### Bytes
+
+Immutable sequence of binary data.
+
+#### Methods
+
+| **Name** | **Description** |
+| --- | --- |
+| at
| Get the byte at the given index. |
+| copy
| Create a copy of the `bytes` value. |
+| slice
| Get the slice of the `bytes` value from the given start index to the given end index. |
+| toBase64
| Convert the `bytes` value to a base64 encoded string. |
+| toHex
| Convert the `bytes` value to a hex encoded string. |
+| toRaw
| Convert the `bytes` value to an array of byte values. |
+| tryAt
| Get the byte at the given index, returning nil if the index is out of bounds. |
+
+---
+
+##### `at`
+
+```wing
+at(index: num): num
+```
+
+Get the byte at the given index.
+
+###### `index`Required
+
+- *Type:* num
+
+index of the value to get.
+
+---
+
+##### `copy`
+
+```wing
+copy(): Bytes
+```
+
+Create a copy of the `bytes` value.
+
+##### `slice`
+
+```wing
+slice(startIndex: num, endIndex?: num): Bytes
+```
+
+Get the slice of the `bytes` value from the given start index to the given end index.
+
+###### `startIndex`Required
+
+- *Type:* num
+
+index to start the slice.
+
+---
+
+###### `endIndex`Optional
+
+- *Type:* num
+
+index to end the slice.
+
+---
+
+##### `toBase64`
+
+```wing
+toBase64(): str
+```
+
+Convert the `bytes` value to a base64 encoded string.
+
+##### `toHex`
+
+```wing
+toHex(): str
+```
+
+Convert the `bytes` value to a hex encoded string.
+
+##### `toRaw`
+
+```wing
+toRaw(): MutArray
+```
+
+Convert the `bytes` value to an array of byte values.
+
+##### `tryAt`
+
+```wing
+tryAt(index: num): num?
+```
+
+Get the byte at the given index, returning nil if the index is out of bounds.
+
+###### `index`Required
+
+- *Type:* num
+
+index of the value to get.
+
+---
+
+#### Static Functions
+
+| **Name** | **Description** |
+| --- | --- |
+| concat
| Concatenate multiple `bytes` values. |
+| fromBase64
| Create a new `bytes` value from a base64 encoded string. |
+| fromHex
| Create a new `bytes` value from a hex encoded string. |
+| fromRaw
| Create a new `bytes` value from an array of byte values. |
+| fromString
| Create a new `bytes` value from a string. |
+| zeros
| Create a new `bytes` value with the given length, filled with zeros. |
+
+---
+
+##### `concat`
+
+```wing
+Bytes.concat(...values: Array);
+```
+
+Concatenate multiple `bytes` values.
+
+###### `values`Required
+
+- *Type:* Bytes
+
+the `bytes` values to concatenate.
+
+---
+
+##### `fromBase64`
+
+```wing
+Bytes.fromBase64(base64: str);
+```
+
+Create a new `bytes` value from a base64 encoded string.
+
+###### `base64`Required
+
+- *Type:* str
+
+The base64 encoded string to create the `bytes` from.
+
+---
+
+##### `fromHex`
+
+```wing
+Bytes.fromHex(hex: str);
+```
+
+Create a new `bytes` value from a hex encoded string.
+
+###### `hex`Required
+
+- *Type:* str
+
+The hex encoded string to create the `bytes` from.
+
+---
+
+##### `fromRaw`
+
+```wing
+Bytes.fromRaw(values: MutArray);
+```
+
+Create a new `bytes` value from an array of byte values.
+
+###### `values`Required
+
+- *Type:* MutArray<num>
+
+The byte values to create the `bytes` from.
+
+---
+
+##### `fromString`
+
+```wing
+Bytes.fromString(value: str);
+```
+
+Create a new `bytes` value from a string.
+
+###### `value`Required
+
+- *Type:* str
+
+The string to create the `bytes` from.
+
+---
+
+##### `zeros`
+
+```wing
+Bytes.zeros(length: num);
+```
+
+Create a new `bytes` value with the given length, filled with zeros.
+
+###### `length`Required
+
+- *Type:* num
+
+The length of the new `bytes` value.
+
+---
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| length
| num
| The length of the bytes. |
+
+---
+
+##### `length`Required
+
+```wing
+length: num;
+```
+
+- *Type:* num
+
+The length of the bytes.
+
+---
+
+
+## Structs
+
+### BytesToStringOptions
+
+Options for converting a `bytes` value to a string.
+
+#### Initializer
+
+```wing
+let BytesToStringOptions = BytesToStringOptions{ ... };
+```
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| encoding
| str
| The encoding to use when converting the `bytes` value to a string. |
+
+---
+
+##### `encoding`Required
+
+```wing
+encoding: str;
+```
+
+- *Type:* str
+- *Default:* "utf-8"
+
+The encoding to use when converting the `bytes` value to a string.
+
+> [https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings](https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings)
+
+---
+
+
diff --git a/docs/api/05-language-reference.md b/docs/api/05-language-reference.md
index 236e9c050f2..6433997d547 100644
--- a/docs/api/05-language-reference.md
+++ b/docs/api/05-language-reference.md
@@ -1233,14 +1233,14 @@ let rawData: bytes = bytes.fromRaw([104, 101, 108, 108, 111]);
let rawString: bytes = bytes.fromString("hello");
let base64: bytes = bytes.fromBase64("aGVsbG8=");
let hex: bytes = bytes.fromHex("68656c6c6f");
-let zeroes: bytes = bytes.alloc(20); // allocates 20 zeroed bytes
+let zeros: bytes = bytes.zeros(20); // allocates 20 zeroed bytes
// mutable initializers
let rawDataMut: mutbytes = mutbytes.fromRaw([104, 101, 108, 108, 111]);
let rawStringMut: mutbytes = mutbytes.fromString("hello");
let base64Mut: mutbytes = mutbytes.fromBase64("aGVsbG8=");
let hexMut: mutbytes = mutbytes.fromHex("68656c6c6f");
-let zeroesMut: mutbytes = mutbytes.alloc(20); // allocates 20 zeroed bytes
+let zerosMut: mutbytes = mutbytes.zeros(20); // allocates 20 zeroed bytes
```
#### Converting bytes to other types
@@ -1262,7 +1262,7 @@ let asBytes: bytes = asMutBytes.copy();
#### Working with bytes
```TS
-let concatenated: bytes = rawData.concat(rawData);
+let concatenated: bytes = bytes.concat(rawData, rawData, rawData);
let sliced: bytes = rawData.slice(1, 3);
let length: num = rawData.length;
diff --git a/packages/@winglang/sdk/src/fs/fs.ts b/packages/@winglang/sdk/src/fs/fs.ts
index be0f46c4edc..bc252144926 100644
--- a/packages/@winglang/sdk/src/fs/fs.ts
+++ b/packages/@winglang/sdk/src/fs/fs.ts
@@ -7,6 +7,7 @@ import * as yaml from "yaml";
import { InflightClient } from "../core";
import { normalPath } from "../shared/misc";
import { Datetime, Json } from "../std";
+import { Bytes } from "../std/bytes";
/**
* Custom settings for reading from a file
@@ -386,6 +387,30 @@ export class Util {
return undefined;
}
}
+
+ /**
+ * Read the contents of the file as bytes.
+ * @param filepath The file path.
+ * @returns The bytes contained in the file.
+ */
+ public static readBytes(filepath: string): Bytes {
+ const buf = fs.readFileSync(filepath);
+ return Bytes._fromUtf8Array(buf);
+ }
+
+ /**
+ * If the file exists, read the contents of the file as bytes; otherwise, return `undefined`.
+ * @param filepath The file path.
+ * @returns The bytes contained in the file, `undefined` otherwise.
+ */
+ public static tryReadBytes(filepath: string): Bytes | undefined {
+ try {
+ return Util.readBytes(filepath);
+ } catch {
+ return undefined;
+ }
+ }
+
/**
* Writes data to a file, replacing the file if it already exists.
* @param filepath The file path that needs to be written.
@@ -423,6 +448,21 @@ export class Util {
fs.writeFileSync(filepath, contents.join("---\n"));
}
+ /**
+ * Write bytes to a file, replacing the file if it already exists.
+ * @param filepath The file path that needs to be written.
+ * @param data The bytes to write.
+ * @param options The `encoding` can be set to specify the character encoding. And the `flag` can be set to specify the attributes.
+ * If a flag is not provided, it defaults to `"w"`.
+ */
+ public static writeBytes(
+ filepath: string,
+ data: Bytes,
+ options?: WriteFileOptions
+ ): void {
+ fs.writeFileSync(filepath, data._data, options);
+ }
+
/**
* Appends new data to the end of an existing file
* @param filepath The file path that needs to be appended.
diff --git a/packages/@winglang/sdk/src/helpers.ts b/packages/@winglang/sdk/src/helpers.ts
index 12332d23f0d..1e7e46492f0 100644
--- a/packages/@winglang/sdk/src/helpers.ts
+++ b/packages/@winglang/sdk/src/helpers.ts
@@ -7,6 +7,7 @@ import type { Construct } from "constructs";
import { parse } from "dotenv";
import { expand } from "dotenv-expand";
import type { Resource } from "./std";
+import type { Bytes } from "./std/bytes";
import type { Node } from "./std/node";
// since we moved from node:18 to node:20 the deepStrictEqual doesn't work as expected.
// https://github.com/winglang/wing/issues/4444
@@ -64,6 +65,10 @@ export function unwrap(value: T): T | never {
}
export function lookup(obj: any, index: string | number): any {
+ if (isBytes(obj)) {
+ obj = obj._data;
+ }
+
checkIndex(index);
if (typeof index === "number") {
@@ -90,6 +95,10 @@ export function assign(
kind: "=" | "+=" | "-=",
value: any
) {
+ if (isBytes(obj)) {
+ obj = obj._data;
+ }
+
checkIndex(index);
if (typeof index === "number") {
@@ -126,7 +135,12 @@ function checkIndex(index: string | number) {
}
function checkArrayAccess(obj: any, index: number): number {
- if (!Array.isArray(obj) && !Buffer.isBuffer(obj) && typeof obj !== "string") {
+ if (
+ !Array.isArray(obj) &&
+ !Buffer.isBuffer(obj) &&
+ !(obj instanceof Uint8Array) &&
+ typeof obj !== "string"
+ ) {
throw new TypeError(
"Index is a number but collection is not an array or string"
);
@@ -142,6 +156,12 @@ function checkArrayAccess(obj: any, index: number): number {
return index;
}
+function isBytes(obj: any): obj is Bytes {
+ return (
+ typeof obj === "object" && obj !== null && obj._data instanceof Uint8Array
+ );
+}
+
export function createExternRequire(dirname: string) {
return (externPath: string) => {
// using eval to always avoid bundling
diff --git a/packages/@winglang/sdk/src/std/bytes.ts b/packages/@winglang/sdk/src/std/bytes.ts
new file mode 100644
index 00000000000..deeeae31afe
--- /dev/null
+++ b/packages/@winglang/sdk/src/std/bytes.ts
@@ -0,0 +1,197 @@
+// These classes are used by Wing to create the built-in `bytes` type.
+// They should not be consumed directly by users.
+import { InflightClient } from "../core";
+
+/**
+ * Options for converting a `bytes` value to a string.
+ */
+export interface BytesToStringOptions {
+ /**
+ * The encoding to use when converting the `bytes` value to a string.
+ * @default "utf-8"
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings
+ */
+ readonly encoding: string;
+}
+
+// TODO: is there a way so we can avoid wrapping the Uint8Array data in a class?
+// e.g. so compiled code for bytes.fromString("hello") can just return a Uint8Array
+
+/**
+ * Immutable sequence of binary data.
+ */
+export class Bytes {
+ /**
+ * @internal
+ */
+ public static _toInflightType(): string {
+ return InflightClient.forType(__filename, this.name);
+ }
+
+ /** @internal */
+ public static _fromUtf8Array(data: Uint8Array): Bytes {
+ return new Bytes(data);
+ }
+
+ /**
+ * Create a new `bytes` value from an array of byte values
+ * @param values - The byte values to create the `bytes` from
+ * @returns a new `bytes` value containing the byte values
+ */
+ public static fromRaw(values: Array): Bytes {
+ return new Bytes(new Uint8Array(values));
+ }
+
+ /**
+ * Create a new `bytes` value from a string
+ * @param value - The string to create the `bytes` from
+ * @returns a new `bytes` value containing the string
+ */
+ public static fromString(value: string): Bytes {
+ return new Bytes(new TextEncoder().encode(value));
+ }
+
+ /**
+ * Create a new `bytes` value from a base64 encoded string
+ * @param base64 - The base64 encoded string to create the `bytes` from
+ * @returns a new `bytes` value containing the decoded bytes
+ */
+ public static fromBase64(base64: string): Bytes {
+ const binaryString = atob(base64);
+ const bytes = new Uint8Array(binaryString.length);
+ for (let i = 0; i < binaryString.length; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+ return new Bytes(bytes);
+ }
+
+ /**
+ * Create a new `bytes` value from a hex encoded string
+ * @param hex - The hex encoded string to create the `bytes` from
+ * @returns a new `bytes` value containing the decoded bytes
+ */
+ public static fromHex(hex: string): Bytes {
+ const bytes = new Uint8Array(hex.length / 2);
+ for (let i = 0; i < hex.length; i += 2) {
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
+ }
+ return new Bytes(bytes);
+ }
+
+ /**
+ * Create a new `bytes` value with the given length, filled with zeros
+ * @param length - The length of the new `bytes` value
+ * @returns a new `bytes` value with the given length, filled with zeros
+ */
+ public static zeros(length: number): Bytes {
+ return new Bytes(new Uint8Array(length));
+ }
+
+ /**
+ * Concatenate multiple `bytes` values
+ * @param values the `bytes` values to concatenate
+ * @returns a new `bytes` value with the bytes from the input values concatenated together
+ */
+ public static concat(...values: Array): Bytes {
+ const totalLength = values.reduce((acc, bytes) => acc + bytes.length, 0);
+ const newData = new Uint8Array(totalLength);
+ let offset = 0;
+ for (const bytes of values) {
+ newData.set(bytes._data, offset);
+ offset += bytes.length;
+ }
+ return new Bytes(newData);
+ }
+
+ /** @internal */
+ public readonly _data: Uint8Array;
+
+ private constructor(data: Uint8Array) {
+ this._data = data;
+ }
+
+ /**
+ * Convert the `bytes` value to an array of byte values
+ * @returns an array of byte values
+ */
+ public toRaw(): Array {
+ return Array.from(this._data);
+ }
+
+ /**
+ * Convert the `bytes` value to a string
+ * @param options - The options to use when converting the `bytes` value to a string
+ * @returns a decoded string
+ */
+ public toString(options?: BytesToStringOptions): string {
+ return new TextDecoder(options?.encoding).decode(this._data);
+ }
+
+ /**
+ * Convert the `bytes` value to a base64 encoded string
+ * @returns a base64 encoded string
+ */
+ public toBase64(): string {
+ return btoa(String.fromCharCode(...this._data));
+ }
+
+ /**
+ * Convert the `bytes` value to a hex encoded string
+ * @returns a hex encoded string
+ */
+ public toHex(): string {
+ return Array.from(this._data)
+ .map((byte) => byte.toString(16).padStart(2, "0"))
+ .join("");
+ }
+
+ /**
+ * Create a copy of the `bytes` value
+ * @returns a new `bytes` value with the same byte values as the original
+ */
+ public copy(): Bytes {
+ return new Bytes(this._data.slice());
+ }
+
+ /**
+ * The length of the bytes
+ * @returns the length of the bytes
+ */
+ public get length(): number {
+ return this._data.length;
+ }
+
+ /**
+ * Get the byte at the given index
+ * @param index index of the value to get
+ * @returns the byte value (0-255) at the given index
+ */
+ public at(index: number): number {
+ if (index < 0 || index >= this.length) {
+ throw new Error("Index out of bounds");
+ }
+ return this._data[index];
+ }
+
+ /**
+ * Get the byte at the given index, returning nil if the index is out of bounds.
+ * @param index index of the value to get
+ * @returns the byte value (0-255) at the given index, or nil if the index is out of bounds
+ */
+ public tryAt(index: number): number | undefined {
+ if (index < 0 || index >= this.length) {
+ return undefined;
+ }
+ return this._data[index];
+ }
+
+ /**
+ * Get the slice of the `bytes` value from the given start index to the given end index
+ * @param startIndex index to start the slice
+ * @param endIndex index to end the slice
+ * @returns a new `bytes` value with the bytes from the start index to the end index
+ */
+ public slice(startIndex: number, endIndex?: number): Bytes {
+ return new Bytes(this._data.slice(startIndex, endIndex));
+ }
+}
diff --git a/packages/@winglang/sdk/src/std/index.ts b/packages/@winglang/sdk/src/std/index.ts
index d5f77f4e326..52451e6243a 100644
--- a/packages/@winglang/sdk/src/std/index.ts
+++ b/packages/@winglang/sdk/src/std/index.ts
@@ -1,5 +1,6 @@
export * from "./array";
export * from "./bool";
+export * from "./bytes";
export * from "./datetime";
export * from "./duration";
export * from "./generics";
diff --git a/packages/@winglang/tree-sitter-wing/grammar.js b/packages/@winglang/tree-sitter-wing/grammar.js
index eb4c91ea436..7a8bdda1494 100644
--- a/packages/@winglang/tree-sitter-wing/grammar.js
+++ b/packages/@winglang/tree-sitter-wing/grammar.js
@@ -539,7 +539,8 @@ module.exports = grammar({
"void",
"duration",
"datetime",
- "regex"
+ "regex",
+ "bytes"
),
initializer: ($) =>
diff --git a/packages/@winglang/tree-sitter-wing/src/grammar.json b/packages/@winglang/tree-sitter-wing/src/grammar.json
index a531bc822fe..fe22450e4ab 100644
--- a/packages/@winglang/tree-sitter-wing/src/grammar.json
+++ b/packages/@winglang/tree-sitter-wing/src/grammar.json
@@ -2929,6 +2929,10 @@
{
"type": "STRING",
"value": "regex"
+ },
+ {
+ "type": "STRING",
+ "value": "bytes"
}
]
},
diff --git a/packages/@winglang/wingc/src/ast.rs b/packages/@winglang/wingc/src/ast.rs
index 305edb71a31..1c151d3b3bd 100644
--- a/packages/@winglang/wingc/src/ast.rs
+++ b/packages/@winglang/wingc/src/ast.rs
@@ -131,6 +131,7 @@ pub enum TypeAnnotationKind {
Duration,
Datetime,
Regex,
+ Bytes,
Void,
Json,
MutJson,
@@ -207,6 +208,7 @@ impl Display for TypeAnnotationKind {
TypeAnnotationKind::Duration => write!(f, "duration"),
TypeAnnotationKind::Datetime => write!(f, "datetime"),
TypeAnnotationKind::Regex => write!(f, "regex"),
+ TypeAnnotationKind::Bytes => write!(f, "bytes"),
TypeAnnotationKind::Void => write!(f, "void"),
TypeAnnotationKind::Json => write!(f, "Json"),
TypeAnnotationKind::MutJson => write!(f, "MutJson"),
diff --git a/packages/@winglang/wingc/src/docs.rs b/packages/@winglang/wingc/src/docs.rs
index 434fdd7b82e..1900389cb25 100644
--- a/packages/@winglang/wingc/src/docs.rs
+++ b/packages/@winglang/wingc/src/docs.rs
@@ -157,6 +157,7 @@ impl Documented for TypeRef {
| Type::Duration
| Type::Datetime
| Type::Regex
+ | Type::Bytes
| Type::Boolean
| Type::Void
| Type::Json(_)
@@ -194,6 +195,7 @@ impl Documented for TypeRef {
| Type::Duration
| Type::Datetime
| Type::Regex
+ | Type::Bytes
| Type::Boolean
| Type::Void
| Type::Json(_)
diff --git a/packages/@winglang/wingc/src/dtsify/extern_dtsify.rs b/packages/@winglang/wingc/src/dtsify/extern_dtsify.rs
index 7fbcd359eba..776b327d9b8 100644
--- a/packages/@winglang/wingc/src/dtsify/extern_dtsify.rs
+++ b/packages/@winglang/wingc/src/dtsify/extern_dtsify.rs
@@ -10,12 +10,13 @@ use crate::{
files::{remove_file, update_file, FilesError},
jsify::codemaker::CodeMaker,
type_check::*,
- WINGSDK_ASSEMBLY_NAME, WINGSDK_DATETIME, WINGSDK_DURATION, WINGSDK_REGEX,
+ WINGSDK_ASSEMBLY_NAME, WINGSDK_BYTES, WINGSDK_DATETIME, WINGSDK_DURATION, WINGSDK_REGEX,
};
const DURATION_FQN: &str = formatcp!("{WINGSDK_ASSEMBLY_NAME}.{WINGSDK_DURATION}");
const DATETIME_FQN: &str = formatcp!("{WINGSDK_ASSEMBLY_NAME}.{WINGSDK_DATETIME}");
const REGEX_FQN: &str = formatcp!("{WINGSDK_ASSEMBLY_NAME}.{WINGSDK_REGEX}");
+const BYTES_FQN: &str = formatcp!("{WINGSDK_ASSEMBLY_NAME}.{WINGSDK_BYTES}");
/// Generates a self-contained .d.ts file for a given extern file.
pub struct ExternDTSifier<'a> {
@@ -158,6 +159,17 @@ impl<'a> ExternDTSifier<'a> {
.unwrap();
self.dtsify_type(regex_type, false)
}
+ Type::Bytes => {
+ let bytes_type = self
+ .types
+ .libraries
+ .lookup_nested_str(BYTES_FQN, None)
+ .unwrap()
+ .0
+ .as_type()
+ .unwrap();
+ self.dtsify_type(bytes_type, false)
+ }
Type::Optional(t) => format!("({}) | undefined", self.dtsify_type(*t, is_inflight)),
Type::Array(t) => format!("(readonly ({})[])", self.dtsify_type(*t, is_inflight)),
Type::MutArray(t) => format!("({})[]", self.dtsify_type(*t, is_inflight)),
diff --git a/packages/@winglang/wingc/src/dtsify/mod.rs b/packages/@winglang/wingc/src/dtsify/mod.rs
index 34c8d8319e5..431dbed8e8d 100644
--- a/packages/@winglang/wingc/src/dtsify/mod.rs
+++ b/packages/@winglang/wingc/src/dtsify/mod.rs
@@ -378,6 +378,7 @@ impl<'a> DTSifier<'a> {
TypeAnnotationKind::Duration => format!("{TYPE_STD}.Duration"),
TypeAnnotationKind::Datetime => format!("{TYPE_STD}.Datetime"),
TypeAnnotationKind::Regex => format!("{TYPE_STD}.Regex"),
+ TypeAnnotationKind::Bytes => format!("{TYPE_STD}.Bytes"),
TypeAnnotationKind::Optional(t) => {
format!("({}) | undefined", self.dtsify_type_annotation(&t, ignore_phase))
}
diff --git a/packages/@winglang/wingc/src/fold.rs b/packages/@winglang/wingc/src/fold.rs
index 0a35536f173..261d2550a2b 100644
--- a/packages/@winglang/wingc/src/fold.rs
+++ b/packages/@winglang/wingc/src/fold.rs
@@ -536,6 +536,7 @@ where
TypeAnnotationKind::Duration => TypeAnnotationKind::Duration,
TypeAnnotationKind::Datetime => TypeAnnotationKind::Datetime,
TypeAnnotationKind::Regex => TypeAnnotationKind::Regex,
+ TypeAnnotationKind::Bytes => TypeAnnotationKind::Bytes,
TypeAnnotationKind::Void => TypeAnnotationKind::Void,
TypeAnnotationKind::Json => TypeAnnotationKind::Json,
TypeAnnotationKind::MutJson => TypeAnnotationKind::MutJson,
diff --git a/packages/@winglang/wingc/src/lib.rs b/packages/@winglang/wingc/src/lib.rs
index cebfc6a1fa3..75a2fec4932 100644
--- a/packages/@winglang/wingc/src/lib.rs
+++ b/packages/@winglang/wingc/src/lib.rs
@@ -106,6 +106,7 @@ const WINGSDK_GENERIC: &'static str = "std.T1";
const WINGSDK_DURATION: &'static str = "std.Duration";
const WINGSDK_DATETIME: &'static str = "std.Datetime";
const WINGSDK_REGEX: &'static str = "std.Regex";
+const WINGSDK_BYTES: &'static str = "std.Bytes";
const WINGSDK_MAP: &'static str = "std.Map";
const WINGSDK_MUT_MAP: &'static str = "std.MutMap";
const WINGSDK_ARRAY: &'static str = "std.Array";
diff --git a/packages/@winglang/wingc/src/lsp/completions.rs b/packages/@winglang/wingc/src/lsp/completions.rs
index 314de292f08..4c84a4fb977 100644
--- a/packages/@winglang/wingc/src/lsp/completions.rs
+++ b/packages/@winglang/wingc/src/lsp/completions.rs
@@ -25,7 +25,9 @@ use crate::{UTIL_CLASS_NAME, WINGSDK_BRINGABLE_MODULES, WINGSDK_STD_MODULE};
use super::sync::check_utf8;
-const BUILTIN_TYPES: [&str; 8] = ["bool", "duration", "Json", "MutJson", "num", "str", "datetime", "regex"];
+const BUILTIN_TYPES: [&str; 9] = [
+ "bool", "duration", "Json", "MutJson", "num", "str", "datetime", "regex", "bytes",
+];
const BUILTIN_GENERICS: [&str; 6] = ["Array", "Map", "MutArray", "MutMap", "MutSet", "Set"];
#[no_mangle]
@@ -1131,6 +1133,7 @@ fn format_symbol_kind_as_completion(name: &str, symbol_kind: &SymbolKind) -> Opt
| Type::Duration
| Type::Datetime
| Type::Regex
+ | Type::Bytes
| Type::Boolean
| Type::Void
| Type::Json(_)
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion.snap
index 4eeb4d6499e..88c03620462 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion.snap
@@ -56,6 +56,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion_partial.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion_partial.snap
index 8b052f0e78e..21e3e16a959 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion_partial.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion_partial.snap
@@ -56,6 +56,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/empty.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/empty.snap
index 579eebdf85d..2e2bc7fa22d 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/empty.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/empty.snap
@@ -65,6 +65,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap
index 4ab06337a4a..50adb593bdd 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap
@@ -1,5 +1,5 @@
---
-source: libs/wingc/src/lsp/completions.rs
+source: packages/@winglang/wingc/src/lsp/completions.rs
---
- label: this
kind: 6
@@ -66,6 +66,9 @@ source: libs/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/only_show_symbols_in_scope.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/only_show_symbols_in_scope.snap
index 8664a76341c..eaf30451b3e 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/only_show_symbols_in_scope.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/only_show_symbols_in_scope.snap
@@ -45,6 +45,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_arg_expansion_partial.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_arg_expansion_partial.snap
index 271e4af50c5..edccd1167fb 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_arg_expansion_partial.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_arg_expansion_partial.snap
@@ -65,6 +65,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_definition_types.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_definition_types.snap
index 26c043077fe..4f053723130 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_definition_types.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_definition_types.snap
@@ -1,5 +1,5 @@
---
-source: libs/wingc/src/lsp/completions.rs
+source: packages/@winglang/wingc/src/lsp/completions.rs
---
- label: Foo
kind: 22
@@ -22,6 +22,9 @@ source: libs/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_literal_value.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_literal_value.snap
index 106ee1c1519..280c0e22884 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_literal_value.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_literal_value.snap
@@ -47,6 +47,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_show_values.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_show_values.snap
index 2870bfba730..7e2f2e11bfc 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_show_values.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_show_values.snap
@@ -51,6 +51,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/type_annotation_shows_struct.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/type_annotation_shows_struct.snap
index 067f932d154..cd12b645328 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/type_annotation_shows_struct.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/type_annotation_shows_struct.snap
@@ -1,5 +1,5 @@
---
-source: libs/wingc/src/lsp/completions.rs
+source: packages/@winglang/wingc/src/lsp/completions.rs
---
- label: Foo
kind: 22
@@ -16,6 +16,9 @@ source: libs/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/type_parameter.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/type_parameter.snap
index 4d715c940df..1a8974047ea 100644
--- a/packages/@winglang/wingc/src/lsp/snapshots/completions/type_parameter.snap
+++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/type_parameter.snap
@@ -1,5 +1,5 @@
---
-source: libs/wingc/src/lsp/completions.rs
+source: packages/@winglang/wingc/src/lsp/completions.rs
---
- label: Json
kind: 14
@@ -10,6 +10,9 @@ source: libs/wingc/src/lsp/completions.rs
- label: bool
kind: 14
sortText: kl|zybool
+- label: bytes
+ kind: 14
+ sortText: kl|zybytes
- label: datetime
kind: 14
sortText: kl|zydatetime
diff --git a/packages/@winglang/wingc/src/lsp/symbol_locator.rs b/packages/@winglang/wingc/src/lsp/symbol_locator.rs
index 083aac1fbdc..6b02b4d28b0 100644
--- a/packages/@winglang/wingc/src/lsp/symbol_locator.rs
+++ b/packages/@winglang/wingc/src/lsp/symbol_locator.rs
@@ -142,6 +142,7 @@ impl<'a> SymbolLocator<'a> {
| Type::Duration
| Type::Datetime
| Type::Regex
+ | Type::Bytes
| Type::Boolean => {
if let Some((std_type, ..)) = self.types.get_std_class(&type_) {
if let Some(t) = std_type.as_type_ref() {
diff --git a/packages/@winglang/wingc/src/parser.rs b/packages/@winglang/wingc/src/parser.rs
index 91c0c6f9f51..67da995f7c9 100644
--- a/packages/@winglang/wingc/src/parser.rs
+++ b/packages/@winglang/wingc/src/parser.rs
@@ -121,6 +121,7 @@ static RESERVED_WORDS: phf::Set<&'static str> = phf_set! {
"duration",
"datetime",
"regex",
+ "bytes",
"bool",
"Json",
"MutJson",
@@ -2032,6 +2033,10 @@ impl<'s> Parser<'s> {
kind: TypeAnnotationKind::Regex,
span,
}),
+ "bytes" => Ok(TypeAnnotation {
+ kind: TypeAnnotationKind::Bytes,
+ span,
+ }),
"void" => Ok(TypeAnnotation {
kind: TypeAnnotationKind::Void,
span,
diff --git a/packages/@winglang/wingc/src/type_check.rs b/packages/@winglang/wingc/src/type_check.rs
index 62fe866f65f..fca22eb373c 100644
--- a/packages/@winglang/wingc/src/type_check.rs
+++ b/packages/@winglang/wingc/src/type_check.rs
@@ -29,9 +29,9 @@ use crate::visit_stmt_before_super::{CheckSuperCtorLocationVisitor, CheckValidBe
use crate::visit_types::{VisitType, VisitTypeMut};
use crate::{
debug, CONSTRUCT_BASE_CLASS, CONSTRUCT_BASE_INTERFACE, CONSTRUCT_NODE_PROPERTY, DEFAULT_PACKAGE_NAME,
- UTIL_CLASS_NAME, WINGSDK_APP, WINGSDK_ARRAY, WINGSDK_ASSEMBLY_NAME, WINGSDK_BRINGABLE_MODULES, WINGSDK_DATETIME,
- WINGSDK_DURATION, WINGSDK_GENERIC, WINGSDK_IRESOURCE, WINGSDK_JSON, WINGSDK_MAP, WINGSDK_MUT_ARRAY, WINGSDK_MUT_JSON,
- WINGSDK_MUT_MAP, WINGSDK_MUT_SET, WINGSDK_NODE, WINGSDK_REGEX, WINGSDK_RESOURCE, WINGSDK_SET,
+ UTIL_CLASS_NAME, WINGSDK_APP, WINGSDK_ARRAY, WINGSDK_ASSEMBLY_NAME, WINGSDK_BRINGABLE_MODULES, WINGSDK_BYTES,
+ WINGSDK_DATETIME, WINGSDK_DURATION, WINGSDK_GENERIC, WINGSDK_IRESOURCE, WINGSDK_JSON, WINGSDK_MAP, WINGSDK_MUT_ARRAY,
+ WINGSDK_MUT_JSON, WINGSDK_MUT_MAP, WINGSDK_MUT_SET, WINGSDK_NODE, WINGSDK_REGEX, WINGSDK_RESOURCE, WINGSDK_SET,
WINGSDK_SIM_IRESOURCE_FQN, WINGSDK_STD_MODULE, WINGSDK_STRING, WINGSDK_STRUCT,
};
use camino::{Utf8Path, Utf8PathBuf};
@@ -231,6 +231,7 @@ pub enum Type {
Duration,
Datetime,
Regex,
+ Bytes,
Boolean,
Void,
/// Immutable Json literals may store extra information about their known data
@@ -955,6 +956,7 @@ impl Display for Type {
Type::Duration => write!(f, "duration"),
Type::Datetime => write!(f, "datetime"),
Type::Regex => write!(f, "regex"),
+ Type::Bytes => write!(f, "bytes"),
Type::Boolean => write!(f, "bool"),
Type::Void => write!(f, "void"),
Type::Json(_) => write!(f, "Json"),
@@ -1330,6 +1332,7 @@ impl TypeRef {
Type::Duration => false,
Type::Datetime => false,
Type::Regex => false,
+ Type::Bytes => false,
Type::Inferred(_) => false,
Type::Set(_) => false,
Type::MutSet(_) => false,
@@ -1480,6 +1483,7 @@ pub struct Types {
duration_idx: usize,
datetime_idx: usize,
regex_idx: usize,
+ bytes_idx: usize,
anything_idx: usize,
void_idx: usize,
json_idx: usize,
@@ -1519,6 +1523,8 @@ impl Types {
let datetime_idx = types.len() - 1;
types.push(Box::new(Type::Regex));
let regex_idx = types.len() - 1;
+ types.push(Box::new(Type::Bytes));
+ let bytes_idx = types.len() - 1;
types.push(Box::new(Type::Anything));
let anything_idx = types.len() - 1;
types.push(Box::new(Type::Void));
@@ -1545,6 +1551,7 @@ impl Types {
duration_idx,
datetime_idx,
regex_idx,
+ bytes_idx,
anything_idx,
void_idx,
json_idx,
@@ -1610,6 +1617,10 @@ impl Types {
self.get_typeref(self.regex_idx)
}
+ pub fn bytes(&self) -> TypeRef {
+ self.get_typeref(self.bytes_idx)
+ }
+
pub fn anything(&self) -> TypeRef {
self.get_typeref(self.anything_idx)
}
@@ -1879,6 +1890,7 @@ impl Types {
Type::Duration => "Duration",
Type::Datetime => "Datetime",
Type::Regex => "Regex",
+ Type::Bytes => "Bytes",
Type::Json(_) => "Json",
Type::MutJson => "MutJson",
Type::Array(_) => "Array",
@@ -4190,6 +4202,7 @@ See https://www.winglang.io/docs/concepts/application-tree for more information.
TypeAnnotationKind::Duration => self.types.duration(),
TypeAnnotationKind::Datetime => self.types.datetime(),
TypeAnnotationKind::Regex => self.types.regex(),
+ TypeAnnotationKind::Bytes => self.types.bytes(),
TypeAnnotationKind::Void => self.types.void(),
TypeAnnotationKind::Json => self.types.json(),
TypeAnnotationKind::MutJson => self.types.mut_json(),
@@ -5170,6 +5183,7 @@ See https://www.winglang.io/docs/concepts/application-tree for more information.
| Type::Duration
| Type::Datetime
| Type::Regex
+ | Type::Bytes
| Type::Boolean
| Type::Void
| Type::Nil
@@ -6223,6 +6237,10 @@ See https://www.winglang.io/docs/concepts/application-tree for more information.
name: "Regex".to_string(),
span: symbol.span.clone(),
}),
+ "bytes" => Some(Symbol {
+ name: "Bytes".to_string(),
+ span: symbol.span.clone(),
+ }),
"str" => Some(Symbol {
name: "String".to_string(),
span: symbol.span.clone(),
@@ -6626,6 +6644,10 @@ See https://www.winglang.io/docs/concepts/application-tree for more information.
ResolveReferenceResult::Location(instance_type, self.types.string())
}
+ Type::Bytes => {
+ self.validate_type(index_type, self.types.number(), index);
+ ResolveReferenceResult::Location(instance_type, self.types.bytes())
+ }
Type::Number
| Type::Duration
| Type::Datetime
@@ -6745,6 +6767,12 @@ See https://www.winglang.io/docs/concepts/application-tree for more information.
false,
env,
),
+ Type::Bytes => self.get_property_from_class_like(
+ lookup_known_type(WINGSDK_BYTES, env).as_class().unwrap(),
+ property,
+ false,
+ env,
+ ),
Type::Struct(ref s) => self.get_property_from_class_like(s, property, true, env),
_ => self.spanned_error_with_var(property, "Property not found").0,
}
@@ -7408,6 +7436,7 @@ pub fn fully_qualify_std_type(type_: &str) -> String {
"duration" => "Duration",
"datetime" => "Datetime",
"regex" => "Regex",
+ "bytes" => "Bytes",
"str" => "String",
"num" => "Number",
"bool" => "Boolean",
diff --git a/packages/@winglang/wingc/src/type_check/inference_visitor.rs b/packages/@winglang/wingc/src/type_check/inference_visitor.rs
index 375803a5c14..0544f8e93d1 100644
--- a/packages/@winglang/wingc/src/type_check/inference_visitor.rs
+++ b/packages/@winglang/wingc/src/type_check/inference_visitor.rs
@@ -111,6 +111,7 @@ impl<'a> crate::visit_types::VisitType<'_> for InferenceVisitor<'a> {
| Type::Duration
| Type::Datetime
| Type::Regex
+ | Type::Bytes
| Type::Boolean
| Type::Void
| Type::Json(_)
diff --git a/packages/@winglang/wingc/src/type_check/jsii_importer.rs b/packages/@winglang/wingc/src/type_check/jsii_importer.rs
index 86ddd0fa3a9..1247039bda6 100644
--- a/packages/@winglang/wingc/src/type_check/jsii_importer.rs
+++ b/packages/@winglang/wingc/src/type_check/jsii_importer.rs
@@ -10,8 +10,8 @@ use crate::{
Class, FunctionParameter, FunctionSignature, Interface, ResolveSource, Struct, SymbolKind, Type, TypeRef, Types,
CLASS_INIT_NAME,
},
- CONSTRUCT_BASE_CLASS, CONSTRUCT_BASE_INTERFACE, WINGSDK_ASSEMBLY_NAME, WINGSDK_DATETIME, WINGSDK_DURATION,
- WINGSDK_JSON, WINGSDK_MUT_JSON, WINGSDK_REGEX, WINGSDK_RESOURCE,
+ CONSTRUCT_BASE_CLASS, CONSTRUCT_BASE_INTERFACE, WINGSDK_ASSEMBLY_NAME, WINGSDK_BYTES, WINGSDK_DATETIME,
+ WINGSDK_DURATION, WINGSDK_JSON, WINGSDK_MUT_JSON, WINGSDK_REGEX, WINGSDK_RESOURCE,
};
use colored::Colorize;
use indexmap::IndexMap;
@@ -108,6 +108,8 @@ impl<'a> JsiiImporter<'a> {
self.wing_types.datetime()
} else if type_fqn == &format!("{}.{}", WINGSDK_ASSEMBLY_NAME, WINGSDK_REGEX) {
self.wing_types.regex()
+ } else if type_fqn == &format!("{}.{}", WINGSDK_ASSEMBLY_NAME, WINGSDK_BYTES) {
+ self.wing_types.bytes()
} else if type_fqn == &format!("{}.{}", WINGSDK_ASSEMBLY_NAME, WINGSDK_JSON) {
self.wing_types.json()
} else if type_fqn == &format!("{}.{}", WINGSDK_ASSEMBLY_NAME, WINGSDK_MUT_JSON) {
diff --git a/packages/@winglang/wingc/src/visit.rs b/packages/@winglang/wingc/src/visit.rs
index e92bba6f237..97b20a2384e 100644
--- a/packages/@winglang/wingc/src/visit.rs
+++ b/packages/@winglang/wingc/src/visit.rs
@@ -500,6 +500,7 @@ where
TypeAnnotationKind::Duration => {}
TypeAnnotationKind::Datetime => {}
TypeAnnotationKind::Regex => {}
+ TypeAnnotationKind::Bytes => {}
TypeAnnotationKind::Void => {}
TypeAnnotationKind::Json => {}
TypeAnnotationKind::MutJson => {}
diff --git a/packages/@winglang/wingc/src/visit_types.rs b/packages/@winglang/wingc/src/visit_types.rs
index a6447329d8d..6a0c4405b8a 100644
--- a/packages/@winglang/wingc/src/visit_types.rs
+++ b/packages/@winglang/wingc/src/visit_types.rs
@@ -49,6 +49,7 @@ where
| Type::Duration
| Type::Datetime
| Type::Regex
+ | Type::Bytes
| Type::Boolean
| Type::Void
| Type::Json(None)
diff --git a/packages/vscode-wing/syntaxes/wing.tmLanguage.json b/packages/vscode-wing/syntaxes/wing.tmLanguage.json
index 610de4f1a9a..47ecdd6fa23 100644
--- a/packages/vscode-wing/syntaxes/wing.tmLanguage.json
+++ b/packages/vscode-wing/syntaxes/wing.tmLanguage.json
@@ -160,7 +160,7 @@
},
{
"name": "support.class.wing",
- "match": "\\b(void|str|num|bool|any|duration|regex|datetime|stringable|Map|Set|MutMap|MutSet|Array|MutArray|Promise|Json|MutJson)\\b"
+ "match": "\\b(void|str|num|bool|any|duration|regex|bytes|datetime|stringable|Map|Set|MutMap|MutSet|Array|MutArray|Promise|Json|MutJson)\\b"
}
]
},
diff --git a/tests/sdk_tests/std/bytes.test.w b/tests/sdk_tests/std/bytes.test.w
new file mode 100644
index 00000000000..311256e162c
--- /dev/null
+++ b/tests/sdk_tests/std/bytes.test.w
@@ -0,0 +1,175 @@
+bring expect;
+bring fs;
+bring util;
+
+// constructing bytes
+let rawData = bytes.fromRaw([104, 101, 108, 108, 111]);
+let rawString = bytes.fromString("hello");
+let base64 = bytes.fromBase64("aGVsbG8=");
+let hex = bytes.fromHex("68656c6c6f");
+let zeros = bytes.zeros(5);
+
+expect.equal(rawData, rawString);
+expect.equal(rawData, base64);
+expect.equal(rawData, hex);
+expect.equal(rawData.length, zeros.length);
+
+// converting bytes
+let asString: str = rawData.toString();
+let asRaw: Array = rawData.toRaw();
+let asBase64: str = rawData.toBase64();
+let asHex: str = rawData.toHex();
+
+expect.equal(asString, "hello");
+expect.equal(asRaw, [104, 101, 108, 108, 111]);
+expect.equal(asBase64, "aGVsbG8=");
+expect.equal(asHex, "68656c6c6f");
+
+// string decoding
+let someBytes = bytes.fromRaw([195, 169]);
+let someString = someBytes.toString(); // default is "utf-8"
+let someString2 = someBytes.toString(encoding: "windows-1252");
+
+expect.equal(someString, "é");
+expect.equal(someString2, "é");
+
+// indexing bytes
+expect.equal(rawData[0], 104);
+expect.equal(rawData[1], 101);
+expect.equal(rawData[2], 108);
+expect.equal(rawData[3], 108);
+expect.equal(rawData[4], 111);
+
+try {
+ rawData[5];
+ expect.fail("Expected error");
+} catch err {
+ expect.ok(err.contains("out of bounds"));
+}
+
+// concatenating bytes
+let concated = bytes.concat(rawData, rawData, rawData);
+expect.equal(concated.length, rawData.length * 3);
+expect.equal(concated.toString(), "hellohellohello");
+
+// slicing bytes
+let sliced = rawData.slice(1, 3);
+expect.equal(sliced.length, 2);
+expect.equal(sliced.toString(), "el");
+
+// writing and reading bytes to disk
+let tmpdir = fs.mkdtemp("bytes-test-");
+let path = fs.join(tmpdir, "hello.txt");
+fs.writeBytes(path, rawData);
+let readData = fs.readBytes(path);
+expect.equal(readData.toString(), "hello");
+fs.remove(tmpdir);
+
+// test that we can work with larger files
+if util.os() != "win32" {
+ // Creating and reading a 10MB file
+ let tenMB = 10 * 1024 * 1024; // 10MB in bytes
+ let largeTmpdir = fs.mkdtemp("large-bytes-test-");
+ let largePath = fs.join(largeTmpdir, "large-file.bin");
+
+ // Create a 10MB file using dd
+ util.exec("sh", ["-c", "dd if=/dev/urandom of={largePath} bs=1M count=10 2>/dev/null"]);
+
+ // Read the file into memory as bytes
+ let largeBytes = fs.readBytes(largePath);
+
+ // Verify the size
+ expect.equal(largeBytes.length, tenMB);
+
+ // Clean up
+ fs.remove(largeTmpdir);
+}
+
+// === inflight tests ===
+// all of the code above is repeated here to ensure that the code
+// behaves the same on inflight hosts (like AWS Lambda, or the simulator)
+
+test "bytes inflight" {
+ // constructing bytes
+ let rawData = bytes.fromRaw([104, 101, 108, 108, 111]);
+ let rawString = bytes.fromString("hello");
+ let base64 = bytes.fromBase64("aGVsbG8=");
+ let hex = bytes.fromHex("68656c6c6f");
+ let zeros = bytes.zeros(5);
+
+ expect.equal(rawData, rawString);
+ expect.equal(rawData, base64);
+ expect.equal(rawData, hex);
+ expect.equal(rawData.length, zeros.length);
+
+ // converting bytes
+ let asString: str = rawData.toString();
+ let asRaw: Array = rawData.toRaw();
+ let asBase64: str = rawData.toBase64();
+ let asHex: str = rawData.toHex();
+
+ expect.equal(asString, "hello");
+ expect.equal(asRaw, [104, 101, 108, 108, 111]);
+ expect.equal(asBase64, "aGVsbG8=");
+ expect.equal(asHex, "68656c6c6f");
+
+ // string decoding
+ let someBytes = bytes.fromRaw([195, 169]);
+ let someString = someBytes.toString(); // default is "utf-8"
+ let someString2 = someBytes.toString(encoding: "windows-1252");
+
+ expect.equal(someString, "é");
+ expect.equal(someString2, "é");
+
+ // indexing bytes
+ expect.equal(rawData[0], 104);
+ expect.equal(rawData[1], 101);
+ expect.equal(rawData[2], 108);
+ expect.equal(rawData[3], 108);
+ expect.equal(rawData[4], 111);
+
+ try {
+ rawData[5];
+ expect.fail("Expected error");
+ } catch err {
+ expect.ok(err.contains("out of bounds"));
+ }
+
+ // concatenating bytes
+ let concated = bytes.concat(rawData, rawData, rawData);
+ expect.equal(concated.length, rawData.length * 3);
+ expect.equal(concated.toString(), "hellohellohello");
+
+ // slicing bytes
+ let sliced = rawData.slice(1, 3);
+ expect.equal(sliced.length, 2);
+ expect.equal(sliced.toString(), "el");
+
+ // writing and reading bytes to disk
+ let tmpdir = fs.mkdtemp("bytes-test-");
+ let path = fs.join(tmpdir, "hello.txt");
+ fs.writeBytes(path, rawData);
+ let readData = fs.readBytes(path);
+ expect.equal(readData.toString(), "hello");
+ fs.remove(tmpdir);
+
+ // test that we can work with larger files
+ if util.os() != "win32" {
+ // Creating and reading a 10MB file
+ let tenMB = 10 * 1024 * 1024; // 10MB in bytes
+ let largeTmpdir = fs.mkdtemp("large-bytes-test-");
+ let largePath = fs.join(largeTmpdir, "large-file.bin");
+
+ // Create a 10MB file using dd
+ util.exec("sh", ["-c", "dd if=/dev/urandom of={largePath} bs=1M count=10 2>/dev/null"]);
+
+ // Read the file into memory as bytes
+ let largeBytes = fs.readBytes(largePath);
+
+ // Verify the size
+ expect.equal(largeBytes.length, tenMB);
+
+ // Clean up
+ fs.remove(largeTmpdir);
+ }
+}
diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_compile_tf-aws.md
new file mode 100644
index 00000000000..ad223c5d4b2
--- /dev/null
+++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_compile_tf-aws.md
@@ -0,0 +1,20 @@
+# [bytes.test.w](../../../../../../tests/sdk_tests/std/bytes.test.w) | compile | tf-aws
+
+## main.tf.json
+```json
+{
+ "//": {
+ "metadata": {
+ "backend": "local",
+ "stackName": "root"
+ },
+ "outputs": {}
+ },
+ "provider": {
+ "aws": [
+ {}
+ ]
+ }
+}
+```
+
diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_test_sim.md
new file mode 100644
index 00000000000..e6ca0d29e42
--- /dev/null
+++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_test_sim.md
@@ -0,0 +1,12 @@
+# [bytes.test.w](../../../../../../tests/sdk_tests/std/bytes.test.w) | test | sim
+
+## stdout.log
+```log
+pass ─ bytes.test.wsim » root/Default/test:bytes inflight
+
+Tests 1 passed (1)
+Snapshots 1 skipped
+Test Files 1 passed (1)
+Duration
+```
+