@valbuild/next 0.67.0 → 0.68.0

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 CHANGED
@@ -50,7 +50,7 @@ This version of Val is currently an alpha version - the API can be considered re
50
50
  - [String](#string)
51
51
  - [Number](#number)
52
52
  - [Boolean](#boolean)
53
- - [Optional](#optional)
53
+ - [Nullable](#nullable)
54
54
  - [Array](#array)
55
55
  - [Record](#record)
56
56
  - [Object](#object)
@@ -60,7 +60,7 @@ This version of Val is currently an alpha version - the API can be considered re
60
60
 
61
61
  ## Installation
62
62
 
63
- - Make sure you have TypeScript 5+, Next 13.4+ (other meta frameworks will come), React 18.20.+ (other frontend frameworks will come)
63
+ - Make sure you have TypeScript 5+, Next 14+, React 18.20.+
64
64
  - Install the packages (@valbuild/eslint-plugin is recommended but not required):
65
65
 
66
66
  ```sh
@@ -73,146 +73,136 @@ npm install @valbuild/core@latest @valbuild/next@latest @valbuild/eslint-plugin@
73
73
  npx @valbuild/init@latest
74
74
  ```
75
75
 
76
- ### Online mode
76
+ ## Getting started
77
77
 
78
- To make it possible to do online edits directly in your app, head over to [val.build](https://app.val.build), sign up and import your repository.
78
+ ### Create your first Val content file
79
79
 
80
- **NOTE**: your content is yours. No subscription (or similar) is required to host content from your repository.
80
+ Content in Val is always defined in `.val.ts` (or `.js`) files.
81
81
 
82
- If you do not need to edit content online (i.e. not locally), you do not need to sign up.
82
+ **NOTE**: the init script will generate an example Val content file (unless you opt out of it).
83
83
 
84
- **WHY**: to update your code, we need to create a commit. This requires a server. We opted to create a service that does this easily, instead of having a self-hosted alternative, since time spent is money used. Also, the company behind [val.build](https://val.build) is the company that funds the development of this software.
84
+ Val content files are _evaluated_ by Val, therefore they need to abide a set of requirements.
85
+ If you use the eslint plugins these requirements will be enforced. You can also validate val files using the @valbuild/cli: `npx -p @valbuild/cli val validate`.
85
86
 
86
- #### Online mode configuration
87
+ For reference these requirements are:
87
88
 
88
- Once you have setup your project in [val.build](https://app.val.build), you must configure your application to use this project.
89
+ - they must export a default content definition (`c.define`) where the first argument equals the path of the file relative to the `val.config` file; and
90
+ - they must be declared in the `val.modules` file; and
91
+ - they must have a default export that is `c.define`; and
92
+ - they can only import Val related files or types (using `import type { MyType } from "./otherModule.ts"`)
89
93
 
90
- To do this, you must set the following environment variables:
94
+ ### Val content file example
91
95
 
92
- - `VAL_API_KEY`: you get this in your project configuration page.
93
- - `VAL_SECRET`: this is a random secret you can generate. It is used for communication between the UX client and your Next.js application.
96
+ ```ts
97
+ // ./examples/val/example.val.ts
98
+ import { s /* s = schema */, c /* c = content */ } from "../../val.config";
94
99
 
95
- In addition, you need to set the following properties in the `val.config` file:
100
+ /**
101
+ * This is the schema for the content. It defines the structure of the content and the types of each field.
102
+ */
103
+ export const schema = s.object({
104
+ /**
105
+ * Basic text field
106
+ */
107
+ text: s.string(),
108
+ });
96
109
 
97
- - project: This is the fully qualified name of your project. It should look like this: `<team>/<name>`.
98
- - gitBranch: This is the git branch your application is using. In Vercel you can use: `VERCEL_GIT_COMMIT_REF`.
99
- - gitCommit: This is the current git commit your application is running on. In Vercel you can use: `VERCEL_GIT_COMMIT_SHA`
110
+ /**
111
+ * This is the content definition. Add your content below.
112
+ *
113
+ * NOTE: the first argument is the path of the file.
114
+ */
115
+ export default c.define("/examples/val/example.val.ts", schema, {
116
+ text: "Basic text content",
117
+ });
118
+ ```
100
119
 
101
- Example of `val.config.ts`:
120
+ ### The `val.modules` file
121
+
122
+ Once you have created your Val content file, it must be declared in the `val.modules.ts` (or `.js`) file in the project root folder.
123
+
124
+ Example:
102
125
 
103
126
  ```ts
104
- import { initVal } from "@valbuild/next";
127
+ import { modules } from "@valbuild/next";
128
+ import { config } from "./val.config";
105
129
 
106
- const { s, c, val, config } = initVal({
107
- project: "myteam/myproject",
108
- gitBranch: process.env.VERCEL_GIT_COMMIT_REF,
109
- gitCommit: process.env.VERCEL_GIT_COMMIT_SHA,
110
- });
130
+ export default modules(config, [
131
+ // Add your modules here
132
+ { def: () => import("./examples/val/example.val") },
133
+ ]);
134
+ ```
111
135
 
112
- export type { t } from "@valbuild/next";
113
- export { s, c, val, config };
136
+ ### Using Val in Client Components
137
+
138
+ In client components you can access your content with the `useVal` hook:
139
+
140
+ ```tsx
141
+ // ./app/page.tsx
142
+ "use client";
143
+ import { useVal } from "../val/val.client";
144
+ import exampleVal from "../examples/val/example.val";
145
+
146
+ export default function Home() {
147
+ const { text } = useVal(exampleVal);
148
+ return <main>{text}</main>;
149
+ }
114
150
  ```
115
151
 
116
- ## Getting started
152
+ ### Using Val in React Server Components
117
153
 
118
- ### Create your first Val content file
154
+ In React Server components you can access your content with the `fetchVal` function:
119
155
 
120
- Content in Val is always defined in `.val.ts` files.
156
+ ```tsx
157
+ // ./app/page.tsx
158
+ "use server";
159
+ import { fetchVal } from "../val/val.rsc";
160
+ import exampleVal from "../examples/val/example.val";
161
+
162
+ export default async function Home() {
163
+ const { text } = await fetchVal(exampleVal);
164
+ return <main>{text}</main>;
165
+ }
166
+ ```
121
167
 
122
- **NOTE**: Val also works with `.js` files.
168
+ # Remote Mode
123
169
 
124
- They must export a default content definition (`c.define`) where the first argument equals the path of the file relative to the `val.config.ts` file.
170
+ Enable remote mode to allow editors to update content online (outside of local development) by creating a project at [app.val.build](https://app.val.build).
125
171
 
126
- **NOTE**: `val.ts` files are _evaluated_ by Val, therefore they have a specific set of requirements:
172
+ **NOTE**: Your content remains yours. Hosting content from your repository does not require a subscription. However, to edit content online, a subscription is needed — unless your project is a public repository or qualifies for the free tier. Visit the [pricing page](https://val.build/pricing) for details.
127
173
 
128
- - They must have a default export that is `c.define`, they must have a `export const schema` with the Schema; and
129
- - they CANNOT import anything other than `val.config` and `@valbuild/core`
174
+ **WHY**: Updating code involves creating a commit, which requires a server. We offer a hosted service for simplicity and efficiency, as self-hosted solutions takes time to setup and maintain. Additionally, the [val.build](https://val.build) team funds the ongoing development of this library.
130
175
 
131
- ### Example of a `.val.ts` file
176
+ ## Remote Mode Configuration
132
177
 
133
- ```ts
134
- // ./src/app/content.val.ts
178
+ Once your project is set up in [app.val.build](https://app.val.build), configure your application to use it by setting the following:
135
179
 
136
- import { s, c } from "../../../val.config";
180
+ ### Environment Variables
137
181
 
138
- export const schema = s.object({
139
- title: s.string().optional(), // <- NOTE: optional()
140
- sections: s.array(
141
- s.object({
142
- title: s.string(),
143
- text: s.richtext({
144
- style: {
145
- bold: true, // <- Enables bold in richtext
146
- },
147
- }),
148
- }),
149
- ),
150
- });
182
+ - **`VAL_API_KEY`**: Obtain this from your project's configuration page.
183
+ - **`VAL_SECRET`**: Generate a random secret to secure communication between the UX client and your Next.js application.
151
184
 
152
- export default c.define(
153
- "/src/app/content", // <- NOTE: this must be the same path as the file
154
- schema,
155
- {
156
- title: "My Page",
157
- sections: [
158
- {
159
- title: "Section 1",
160
- text: [
161
- {
162
- tag: "p",
163
- children: [
164
- "Val is",
165
- { tag: "span", styles: ["bold"], children: ["awesome"] },
166
- ],
167
- },
168
- ],
169
- },
170
- ],
171
- },
172
- );
173
- ```
185
+ ### `val.config` Properties
174
186
 
175
- ### Use your content
187
+ Set these properties in the `val.config` file:
176
188
 
177
- In client components you can access your content with the `useVal` hook:
189
+ - **`project`**: The fully qualified name of your project, formatted as `<team>/<name>`.
190
+ - **`gitBranch`**: The Git branch your application uses. For Vercel, use `VERCEL_GIT_COMMIT_REF`.
191
+ - **`gitCommit`**: The current Git commit your application is running on. For Vercel, use `VERCEL_GIT_COMMIT_SHA`.
178
192
 
179
- **NOTE**: Support for React Server Components and server side rendering will come soon.
193
+ ### Example `val.config.ts`
180
194
 
181
- ```tsx
182
- // ./src/app/page.tsx
183
- "use client";
184
- import { NextPage } from "next";
185
- import { useVal } from "./val/val.client";
186
- import contentVal from "./content.val";
195
+ ```ts
196
+ import { initVal } from "@valbuild/next";
187
197
 
188
- const Page: NextPage = () => {
189
- const { title, sections } = useVal(contentVal);
190
- return (
191
- <main>
192
- {title && (
193
- <section>
194
- <h1>{title}</h1>
195
- </section>
196
- )}
197
- {sections.map((section) => (
198
- <section>
199
- <h2>{section.title}</h2>
200
- <ValRichText
201
- theme={{
202
- style: {
203
- bold: "font-bold",
204
- },
205
- }}
206
- >
207
- {section.text}
208
- </ValRichText>
209
- </section>
210
- ))}
211
- </main>
212
- );
213
- };
198
+ const { s, c, val, config } = initVal({
199
+ project: "myteam/myproject",
200
+ gitBranch: process.env.VERCEL_GIT_COMMIT_REF,
201
+ gitCommit: process.env.VERCEL_GIT_COMMIT_SHA,
202
+ });
214
203
 
215
- export default Page;
204
+ export type { t } from "@valbuild/next";
205
+ export { s, c, val, config };
216
206
  ```
217
207
 
218
208
  # Schema types
@@ -241,14 +231,14 @@ import { s } from "./val.config";
241
231
  s.boolean(); // <- Schema<boolean>
242
232
  ```
243
233
 
244
- ## Optional
234
+ ## Nullable
245
235
 
246
- All schema types can be optional. An optional schema creates a union of the type and `null`.
236
+ All schema types can be nullable (optional). A nullable schema creates a union of the type and `null`.
247
237
 
248
238
  ```ts
249
239
  import { s } from "./val.config";
250
240
 
251
- s.string().optional(); // <- Schema<string | null>
241
+ s.string().nullable(); // <- Schema<string | null>
252
242
  ```
253
243
 
254
244
  ## Array
@@ -384,7 +374,7 @@ To add classes to `ValRichText` you can use the theme property:
384
374
  </ValRichText>
385
375
  ```
386
376
 
387
- **NOTE**: if a theme is defined, you must define a mapping for every tag that the you get. What tags you have is decided based on the `options` defined on the `s.richtext()` schema. For example: `s.richtext({ headings: ["h1"]; bold: true; img: true})` forces you to map the class for at least: `h1`, `bold` and `img`:
377
+ **NOTE**: if a theme is defined, you must define a mapping for every tag that the you get. What tags you have is decided based on the `options` defined on the `s.richtext()` schema. For example: `s.richtext({ style: { bold: true } })` requires that you add a `bold` theme.
388
378
 
389
379
  ```tsx
390
380
  <ValRichText
@@ -394,7 +384,7 @@ To add classes to `ValRichText` you can use the theme property:
394
384
  img: null, // either a string or null is required
395
385
  }}
396
386
  >
397
- {content satisfies RichText<{ headings: ["h1"]; bold: true; img: true }>}
387
+ {content}
398
388
  </ValRichText>
399
389
  ```
400
390
 
@@ -404,7 +394,7 @@ To add classes to `ValRichText` you can use the theme property:
404
394
 
405
395
  Vals `RichText` type maps RichText 1-to-1 with semantic HTML5.
406
396
 
407
- If you want to customize the type of elements which are rendered, you can use the `transform` property.
397
+ If you want to customize / override the type of elements which are rendered, you can use the `transform` property.
408
398
 
409
399
  ```tsx
410
400
  <ValRichText
@@ -525,9 +515,9 @@ The `ValImage` component is a wrapper around `next/image` that accepts a Val `Im
525
515
  You can use it like this:
526
516
 
527
517
  ```tsx
528
- const content = useVal(contentVal);
518
+ const content = useVal(contentVal); // schema of contentVal: s.object({ image: s.image() })
529
519
 
530
- return <ValImage src={content.image} alt={content.alt} />;
520
+ return <ValImage src={content.image} />;
531
521
  ```
532
522
 
533
523
  ### Using images in components
@@ -585,7 +575,22 @@ s.union(
585
575
 
586
576
  ## KeyOf
587
577
 
588
- If you need to reference content in another `.val` file you can use the `keyOf` schema.
578
+ You can use `keyOf` to reference a key in a record of a Val module.
579
+
580
+ **NOTE**: currently you must reference keys in Val modules, you cannot reference keys of values nested inside a Val module. This is a feature on the roadmap.
581
+
582
+ ```ts
583
+ const schema = s.record(s.object({ nested: s.record(s.string()) }));
584
+
585
+ export default c.define("/keyof.val.ts", schema, {
586
+ "you-can-reference-me": {
587
+ // <- this can be referenced
588
+ nested: {
589
+ "but-not-me": ":(", // <- this cannot be referenced
590
+ },
591
+ },
592
+ });
593
+ ```
589
594
 
590
595
  ### KeyOf Schema
591
596
 
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import NextImage from "next/image";
2
3
  import { Image } from "@valbuild/react/stega";
3
4
  export type ValImageProps = Omit<React.ComponentProps<typeof NextImage>, "src" | "alt" | "srcset" | "layout" | "objectFit" | "objectPosition" | "lazyBoundary" | "lazyRoot"> & {
@@ -1,5 +1,6 @@
1
+ /// <reference types="react" />
1
2
  export declare const ValProvider: (props: {
2
- children: React.ReactNode | React.ReactNode[];
3
+ children: import("react").ReactNode | import("react").ReactNode[];
3
4
  config: import("@valbuild/core").ValConfig;
4
- disableRefresh?: boolean;
5
+ disableRefresh?: boolean | undefined;
5
6
  }) => import("react/jsx-runtime").JSX.Element;
@@ -44,30 +44,7 @@ export declare const Internal: {
44
44
  [k: string]: string;
45
45
  };
46
46
  ModuleFilePathSep: string;
47
- notFileOp: (op: import("@valbuild/core/patch").Operation) => op is {
48
- op: "add";
49
- path: string[];
50
- value: import("@valbuild/core/dist/declarations/src/patch").JSONValue;
51
- } | {
52
- op: "remove";
53
- path: import("@valbuild/core/dist/declarations/src/fp/array").NonEmptyArray<string>;
54
- } | {
55
- op: "replace";
56
- path: string[];
57
- value: import("@valbuild/core/dist/declarations/src/patch").JSONValue;
58
- } | {
59
- op: "move";
60
- from: import("@valbuild/core/dist/declarations/src/fp/array").NonEmptyArray<string>;
61
- path: string[];
62
- } | {
63
- op: "copy";
64
- from: string[];
65
- path: string[];
66
- } | {
67
- op: "test";
68
- path: string[];
69
- value: import("@valbuild/core/dist/declarations/src/patch").JSONValue;
70
- };
47
+ notFileOp: (op: import("@valbuild/core/patch").Operation) => boolean;
71
48
  isFileOp: (op: import("@valbuild/core/patch").Operation) => op is {
72
49
  op: "file";
73
50
  path: string[];
@@ -9,7 +9,7 @@ declare const initFetchValStega: (config: ValConfig, valApiEndpoints: string, va
9
9
  name: string;
10
10
  value: string;
11
11
  } | undefined;
12
- }>) => <T extends SelectorSource>(selector: T) => Promise<SelectorOf<T> extends GenericSelector<infer S> ? StegaOfSource<S> : never>;
12
+ }>) => <T extends SelectorSource>(selector: T) => Promise<SelectorOf<T> extends GenericSelector<infer S extends import("@valbuild/core").Source, undefined> ? StegaOfSource<S> : never>;
13
13
  type ValNextRscConfig = {
14
14
  draftMode: typeof draftMode;
15
15
  headers: typeof headers;
@@ -2,7 +2,7 @@ import { ValConfig, ValModules } from "@valbuild/core";
2
2
  import type { draftMode } from "next/headers";
3
3
  import { NextResponse } from "next/server";
4
4
  declare const initValNextAppRouter: (valModules: ValModules, config: ValConfig, nextConfig: ValServerNextConfig & {
5
- formatter?: (code: string, filePath: string) => Promise<string> | string;
5
+ formatter?: ((code: string, filePath: string) => Promise<string> | string) | undefined;
6
6
  }) => (req: Request) => Promise<NextResponse<unknown>>;
7
7
  type ValServerNextConfig = {
8
8
  draftMode: typeof draftMode;
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "next",
9
9
  "react"
10
10
  ],
11
- "version": "0.67.0",
11
+ "version": "0.68.0",
12
12
  "scripts": {
13
13
  "typecheck": "tsc --noEmit",
14
14
  "test": "jest"
@@ -45,11 +45,11 @@
45
45
  "exports": true
46
46
  },
47
47
  "dependencies": {
48
- "@valbuild/core": "~0.67.0",
49
- "@valbuild/react": "~0.67.0",
50
- "@valbuild/server": "~0.67.0",
51
- "@valbuild/shared": "~0.67.0",
52
- "@valbuild/ui": "~0.67.0",
48
+ "@valbuild/core": "~0.68.0",
49
+ "@valbuild/react": "~0.68.0",
50
+ "@valbuild/server": "~0.68.0",
51
+ "@valbuild/shared": "~0.68.0",
52
+ "@valbuild/ui": "~0.68.0",
53
53
  "client-only": "^0.0.1",
54
54
  "server-only": "^0.0.1"
55
55
  },
@@ -80,7 +80,7 @@ var initFetchValStega = function initFetchValStega(config, valApiEndpoints, valS
80
80
  case 38:
81
81
  valServer = _context.sent;
82
82
  _context.next = 41;
83
- return valServer["/patches/~"]["GET"]({
83
+ return valServer["/patches"]["GET"]({
84
84
  query: {
85
85
  omit_patch: true,
86
86
  author: undefined,
@@ -99,7 +99,7 @@ var initFetchValStega = function initFetchValStega(config, valApiEndpoints, valS
99
99
  case 44:
100
100
  allPatches = Object.keys(patchesRes.json.patches);
101
101
  _context.next = 47;
102
- return valServer["/sources"]["PUT"]({
102
+ return valServer["/sources/~"]["PUT"]({
103
103
  path: "/",
104
104
  query: {
105
105
  validate_sources: true,
@@ -80,7 +80,7 @@ var initFetchValStega = function initFetchValStega(config, valApiEndpoints, valS
80
80
  case 38:
81
81
  valServer = _context.sent;
82
82
  _context.next = 41;
83
- return valServer["/patches/~"]["GET"]({
83
+ return valServer["/patches"]["GET"]({
84
84
  query: {
85
85
  omit_patch: true,
86
86
  author: undefined,
@@ -99,7 +99,7 @@ var initFetchValStega = function initFetchValStega(config, valApiEndpoints, valS
99
99
  case 44:
100
100
  allPatches = Object.keys(patchesRes.json.patches);
101
101
  _context.next = 47;
102
- return valServer["/sources"]["PUT"]({
102
+ return valServer["/sources/~"]["PUT"]({
103
103
  path: "/",
104
104
  query: {
105
105
  validate_sources: true,
@@ -76,7 +76,7 @@ var initFetchValStega = function initFetchValStega(config, valApiEndpoints, valS
76
76
  case 38:
77
77
  valServer = _context.sent;
78
78
  _context.next = 41;
79
- return valServer["/patches/~"]["GET"]({
79
+ return valServer["/patches"]["GET"]({
80
80
  query: {
81
81
  omit_patch: true,
82
82
  author: undefined,
@@ -95,7 +95,7 @@ var initFetchValStega = function initFetchValStega(config, valApiEndpoints, valS
95
95
  case 44:
96
96
  allPatches = Object.keys(patchesRes.json.patches);
97
97
  _context.next = 47;
98
- return valServer["/sources"]["PUT"]({
98
+ return valServer["/sources/~"]["PUT"]({
99
99
  path: "/",
100
100
  query: {
101
101
  validate_sources: true,