···1313/// A user profile
1414record profile {
1515 /// The user's display name
1616- name: string,
1616+ name!: string,
1717 /// The user's email address
1818- email: string,
1818+ email!: string,
1919 /// When the account was created
2020- createdAt: Datetime,
2020+ createdAt!: Datetime,
2121}
2222```
23232424-This defines a `profile` record with three fields: `name`, `email`, and `createdAt`.
2424+This defines a `profile` record with three required fields: `name`, `email`, and `createdAt`.
25252626## File Naming and Namespaces
2727···114114/// A forum post
115115record post {
116116 /// Post title
117117- title: string,
117117+ title!: string,
118118 /// Post content
119119- body: string,
119119+ body!: string,
120120 /// Post author's DID
121121- author: Did,
121121+ author!: Did,
122122 /// When the post was published
123123- publishedAt: Datetime,
123123+ publishedAt!: Datetime,
124124}
125125```
126126
+16-16
website/content/docs/language-guide/02-fields.md
···7788## Required vs Optional Fields
991010-By default, all fields are required. Use `?` to make a field optional:
1010+By default, all fields are optional. Use `!` to make a field required:
11111212```mlf
1313record user {
1414- name: string, // Required - must be provided
1515- bio?: string, // Optional - can be omitted
1616- email: string, // Required
1717- website?: string, // Optional
1414+ name!: string, // Required - must be provided
1515+ bio: string, // Optional - can be omitted (default)
1616+ email!: string, // Required
1717+ website: string, // Optional (default)
1818}
1919```
2020···155155/// A forum post
156156record post {
157157 /// Post text content
158158- text: string,
158158+ text!: string,
159159160160 /// Post author
161161- author: Did,
161161+ author!: Did,
162162163163 /// When the post was created
164164- createdAt: Datetime,
164164+ createdAt!: Datetime,
165165166166 /// Optional reply count
167167- replyCount?: integer,
167167+ replyCount: integer,
168168169169 /// Whether the post is pinned
170170- isPinned: boolean,
170170+ isPinned!: boolean,
171171172172 /// Optional geographic location
173173- location?: {
174174- name: string,
175175- lat: integer,
176176- lng: integer,
173173+ location: {
174174+ name!: string,
175175+ lat!: integer,
176176+ lng!: integer,
177177 },
178178179179 /// Tags on this post
180180- tags: string[],
180180+ tags!: string[],
181181182182 /// Optional embedded images
183183- images?: Uri[],
183183+ images: Uri[],
184184185185 /// Arbitrary metadata
186186 metadata: unknown,
···33weight = 5
44+++
5566-Unions allow a field to accept multiple types. MLF supports both closed unions (fixed set of types) and open unions (allowing unknown types).
66+Unions allow a field to accept multiple types. MLF supports both open unions (allowing unknown types) and closed unions (fixed set of types).
7788-## Closed Unions
88+## Open Unions
991010-Use the pipe operator `|` to create a union of types:
1010+By default, unions are open. Use the pipe operator `|` to create a union of types:
11111212```mlf
1313def type textPost = {
1414- text: string,
1414+ text!: string,
1515};
16161717def type imagePost = {
1818- image: Uri,
1919- caption?: string,
1818+ image!: Uri,
1919+ caption: string,
2020};
21212222def type videoPost = {
2323- video: Uri,
2424- duration: integer,
2323+ video!: Uri,
2424+ duration!: integer,
2525};
26262727record post {
···2929}
3030```
31313232-The `content` field must be one of these three types. No other types are accepted.
3232+The `content` field accepts these three types, plus any unknown types. This allows for forward compatibility.
33333434-## Open Unions
3434+## Closed Unions
35353636-Add `| _` to allow unknown types for forward compatibility:
3636+Add `| !` to create a closed union that only accepts the listed types:
37373838```mlf
3939record post {
4040- content: textPost | imagePost | _,
4040+ content: textPost | imagePost | !,
4141}
4242```
43434444-Now the system knows about `textPost` and `imagePost`, but will also accept unknown types it hasn't seen before. This is useful when you expect the union to grow in the future.
4444+Now the system will only accept `textPost` and `imagePost`. No other types are allowed. Use closed unions when you want strict type checking.
45454646## Why Use Open Unions?
47474848Open unions help with forward compatibility:
49495050```mlf
5151-// Version 1: Only text and images
5151+// Version 1: Only text and images (open by default)
5252record post {
5353- content: textPost | imagePost | _,
5353+ content: textPost | imagePost,
5454}
55555656// Version 2: Add video support
5757-// Old clients still work because of the `_`
5757+// Old clients still work because unions are open by default
5858def type videoPost = {
5959- video: Uri,
5959+ video!: Uri,
6060};
61616262record post {
6363- content: textPost | imagePost | videoPost | _,
6363+ content: textPost | imagePost | videoPost,
6464}
6565```
66666767-Old clients that don't know about `videoPost` can still handle the lexicon because of the open union.
6767+Old clients that don't know about `videoPost` can still handle the lexicon because of the open union (the default behavior).
68686969## Unions with Inline Objects
7070···7373```mlf
7474record embed {
7575 content: {
7676- text: string,
7676+ text!: string,
7777 } | {
7878- image: Uri,
7878+ image!: Uri,
7979 } | {
8080- link: Uri,
8181- title: string,
8080+ link!: Uri,
8181+ title!: string,
8282 },
8383}
8484```
···91919292```mlf
9393def type mention = {
9494- did: Did,
9595- start: integer,
9696- end: integer,
9494+ did!: Did,
9595+ start!: integer,
9696+ end!: integer,
9797};
98989999def type link = {
100100- uri: Uri,
101101- start: integer,
102102- end: integer,
100100+ uri!: Uri,
101101+ start!: integer,
102102+ end!: integer,
103103};
104104105105def type tag = {
106106- name: string,
107107- start: integer,
108108- end: integer,
106106+ name!: string,
107107+ start!: integer,
108108+ end!: integer,
109109};
110110111111record post {
112112- text: string,
112112+ text!: string,
113113 facets: (mention | link | tag)[],
114114}
115115```
···124124```mlf
125125/// Text content
126126def type textContent = {
127127- text: string constrained {
127127+ text!: string constrained {
128128 maxGraphemes: 2000,
129129 },
130130};
131131132132/// Image content
133133def type imageContent = {
134134- url: Uri,
135135- width: integer,
136136- height: integer,
137137- alt?: string,
134134+ url!: Uri,
135135+ width!: integer,
136136+ height!: integer,
137137+ alt: string,
138138};
139139140140/// File attachment
141141def type fileContent = {
142142- url: Uri,
143143- filename: string,
144144- size: integer,
145145- mimeType: string,
142142+ url!: Uri,
143143+ filename!: string,
144144+ size!: integer,
145145+ mimeType!: string,
146146};
147147148148/// A forum post with different content types
149149record post {
150150 /// Post author
151151- author: Did,
151151+ author!: Did,
152152153153- /// Post content (text, image, or file)
154154- content: textContent | imageContent | fileContent | _,
153153+ /// Post content (text, image, or file) - open union by default
154154+ content: textContent | imageContent | fileContent,
155155156156 /// When the post was created
157157- createdAt: Datetime,
157157+ createdAt!: Datetime,
158158159159 /// Optional reply reference
160160- replyTo?: AtUri,
160160+ replyTo: AtUri,
161161}
162162```
163163164164-The `| _` at the end means future content types can be added without breaking old clients.
164164+Unions are open by default, which means future content types can be added without breaking old clients.
165165166166## What's Next?
167167
+55-55
website/content/docs/language-guide/07-xrpc.md
···2727```mlf
2828/// Get a user profile
2929query getProfile(
3030- actor: Did
3030+ actor!: Did
3131):{
3232- did: Did,
3333- handle: Handle,
3434- displayName?: string,
3232+ did!: Did,
3333+ handle!: Handle,
3434+ displayName: string,
3535};
3636```
3737···3939```mlf
4040/// Search for posts
4141query searchPosts(
4242- q: string,
4343- limit?: integer constrained {
4242+ q!: string,
4343+ limit: integer constrained {
4444 minimum: 1,
4545 maximum: 100,
4646 default: 25,
4747 },
4848- cursor?: string
4848+ cursor: string
4949):{
5050- posts: post[],
5151- cursor?: string,
5050+ posts!: post[],
5151+ cursor: string,
5252};
5353```
54545555**Query returning a record:**
5656```mlf
5757record profile {
5858- did: Did,
5959- handle: Handle,
6060- displayName?: string,
5858+ did!: Did,
5959+ handle!: Handle,
6060+ displayName: string,
6161}
62626363query getProfile(
6464- actor: Did
6464+ actor!: Did
6565):profile;
6666```
6767···7373```mlf
7474/// Create a new post
7575procedure createPost(
7676- text: string constrained {
7676+ text!: string constrained {
7777 minGraphemes: 1,
7878 maxGraphemes: 500,
7979 }
8080):{
8181- uri: AtUri,
8282- cid: Cid,
8181+ uri!: AtUri,
8282+ cid!: Cid,
8383};
8484```
8585···8787```mlf
8888/// Update a user profile
8989procedure updateProfile(
9090- displayName?: string,
9191- bio?: string,
9292- avatar?: blob
9090+ displayName: string,
9191+ bio: string,
9292+ avatar: blob
9393):{
9494- success: boolean,
9494+ success!: boolean,
9595};
9696```
9797···9999```mlf
100100/// Delete a post
101101procedure deletePost(
102102- uri: AtUri
102102+ uri!: AtUri
103103):{
104104- success: boolean,
104104+ success!: boolean,
105105};
106106```
107107···119119```mlf
120120/// Subscribe to posts from specific users
121121subscription subscribePosts(
122122- authors?: Did[]
122122+ authors: Did[]
123123):post;
124124```
125125126126**Subscription with multiple message types:**
127127```mlf
128128def type postCreated = {
129129- post: post,
129129+ post!: post,
130130};
131131132132def type postDeleted = {
133133- uri: AtUri,
133133+ uri!: AtUri,
134134};
135135136136def type postUpdated = {
137137- post: post,
137137+ post!: post,
138138};
139139140140/// Subscribe to post events
···145145```mlf
146146/// Subscribe to repository events
147147subscription subscribeRepos(
148148- cursor?: integer
148148+ cursor: integer
149149):commit | identity | tombstone;
150150```
151151···166166167167```mlf
168168query getPost(
169169- uri: AtUri
169169+ uri!: AtUri
170170):post | error {
171171 /// Post not found
172172 NotFound,
···178178**Procedure with errors:**
179179```mlf
180180procedure createPost(
181181- text: string
181181+ text!: string
182182):{
183183- uri: AtUri,
184184- cid: Cid,
183183+ uri!: AtUri,
184184+ cid!: Cid,
185185} | error {
186186 /// Text exceeds maximum length
187187 TextTooLong,
···203203```mlf
204204query searchPosts(
205205 /// Search query (1-200 characters)
206206- q: string constrained {
206206+ q!: string constrained {
207207 minLength: 1,
208208 maxLength: 200,
209209 },
210210211211 /// Results per page
212212- limit?: integer constrained {
212212+ limit: integer constrained {
213213 minimum: 1,
214214 maximum: 100,
215215 default: 25,
216216 }
217217):{
218218- posts: post[],
218218+ posts!: post[],
219219}
220220```
221221···226226**Inline objects:**
227227```mlf
228228query getStats():{
229229- posts: integer,
230230- followers: integer,
229229+ posts!: integer,
230230+ followers!: integer,
231231};
232232```
233233234234**Named records:**
235235```mlf
236236-query getProfile(did: Did):profile;
236236+query getProfile(did!: Did):profile;
237237```
238238239239**Unions:**
···250250/// A forum post
251251record post {
252252 /// Post title
253253- title: string constrained {
253253+ title!: string constrained {
254254 minGraphemes: 1,
255255 maxGraphemes: 200,
256256 },
257257258258 /// Post content
259259- body: string constrained {
259259+ body!: string constrained {
260260 maxGraphemes: 50000,
261261 },
262262263263 /// Post author
264264- author: Did,
264264+ author!: Did,
265265266266 /// When published
267267- publishedAt: Datetime,
267267+ publishedAt!: Datetime,
268268}
269269270270/// Get a single post
271271query getPost(
272272 /// Post URI
273273- uri: AtUri
273273+ uri!: AtUri
274274):post | error {
275275 /// Post not found
276276 NotFound,
···281281/// List posts by author
282282query listPosts(
283283 /// Author DID
284284- author: Did,
284284+ author!: Did,
285285286286 /// Results per page
287287- limit?: integer constrained {
287287+ limit: integer constrained {
288288 minimum: 1,
289289 maximum: 100,
290290 default: 25,
291291 },
292292293293 /// Pagination cursor
294294- cursor?: string
294294+ cursor: string
295295):{
296296- posts: post[],
297297- cursor?: string,
296296+ posts!: post[],
297297+ cursor: string,
298298};
299299300300/// Create a new post
301301procedure createPost(
302302 /// Post title
303303- title: string constrained {
303303+ title!: string constrained {
304304 minGraphemes: 1,
305305 maxGraphemes: 200,
306306 },
307307308308 /// Post body
309309- body: string constrained {
309309+ body!: string constrained {
310310 maxGraphemes: 50000,
311311 }
312312):{
313313- uri: AtUri,
314314- cid: Cid,
315315- post: post,
313313+ uri!: AtUri,
314314+ cid!: Cid,
315315+ post!: post,
316316} | error {
317317 /// User not authenticated
318318 Unauthorized,
···323323/// Delete a post
324324procedure deletePost(
325325 /// Post URI to delete
326326- uri: AtUri
326326+ uri!: AtUri
327327):{
328328- success: boolean,
328328+ success!: boolean,
329329} | error {
330330 /// Post not found
331331 NotFound,
···336336/// Subscribe to new posts
337337subscription subscribePosts(
338338 /// Optional author filter
339339- author?: Did
339339+ author: Did
340340):post;
341341```
342342