@backtickjs/core 0.1.8 → 0.1.9

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
@@ -38,6 +38,17 @@ export async function Home() {
38
38
 
39
39
  `$node` and `$Counter` are splices: `node` is a server variable, written into the bundle as data; `Counter` is a script. `$useState` is a splice too, of an export the client provides. What can be spliced is `Spliceable`: data, and scripts. [Thinking in Backtick](https://backtickjs.com/docs/thinking-in-backtick) has what can cross, what can't, and how server and client components compose.
40
40
 
41
+ A script every component shares, like a context or a store, is made once with `once`: one value for the bundle, where an expression script would run again at each read.
42
+
43
+ ```tsx
44
+ import { cs, once } from "@backtickjs/core";
45
+ import { createContext, useContext } from "@backtickjs/react";
46
+
47
+ const ThemeContext = once(cs`$createContext("light")`);
48
+ const Label = cs`() => <p>{$useContext($ThemeContext)}</p>`;
49
+ // In a script: <$ThemeContext.Provider value="dark"><$Label /></$ThemeContext.Provider>
50
+ ```
51
+
41
52
  To use a package the client provides beyond what an adapter covers, describe its export with `createImport`. `from` is the specifier the bundle imports, and `version` the semver range of its package that the export works with:
42
53
 
43
54
  ```ts
@@ -51,12 +62,13 @@ const greet = createImport<() => string>({
51
62
  // In a script: cs`<p>{$greet()}</p>`
52
63
  ```
53
64
 
54
- The client must provide that package, and the server must list it in the bundler's `packageVersions` at a version in that range.
65
+ The client must provide that package, and the server must list it in the bundler's `modules` at a version in that range.
55
66
 
56
67
  ## API
57
68
 
58
69
  - `cs`: the tag for client scripts. Compiled at build time; throws if called uncompiled.
59
70
  - `Client<T>`: the type of a script that stands for a `T` on the client.
71
+ - `once(script)`: the script as one value for the bundle, made at its top; each call is one value. Refused for a script that reads a variable of the script around it.
60
72
  - `Spliceable`: what a server may splice into a script.
61
73
  - `Spliced<T>`: what a spliceable value becomes on the client (`Client<U>` becomes `U`; arrays and objects map member by member).
62
74
  - `createImport<T>({ name, from, version })`: an export of a module the client provides, as a value a script can splice.
@@ -68,7 +80,7 @@ The client must provide that package, and the server must list it in the bundler
68
80
  ## Related
69
81
 
70
82
  - [`@backtickjs/bundler`](https://github.com/backtickjs/backtick/tree/main/packages/bundler): runs server components and writes the bundle
71
- - [`@backtickjs/react`](https://github.com/backtickjs/backtick/tree/main/packages/react), [`@backtickjs/react-native`](https://github.com/backtickjs/backtick/tree/main/packages/react-native), [`@backtickjs/solid-js`](https://github.com/backtickjs/backtick/tree/main/packages/solid-js): framework adapters
83
+ - [`@backtickjs/react`](https://github.com/backtickjs/backtick/tree/main/adapters/react), [`@backtickjs/react-native`](https://github.com/backtickjs/backtick/tree/main/adapters/react-native), [`@backtickjs/solid-js`](https://github.com/backtickjs/backtick/tree/main/adapters/solid-js): framework adapters
72
84
  - [`@backtickjs/node-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/node-plugin), [`@backtickjs/bun-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/bun-plugin): compile scripts as your server loads them
73
85
  - [`@backtickjs/tsc`](https://github.com/backtickjs/backtick/tree/main/packages/tsc): type-checks scripts and their splices
74
86
  - [Thinking in Backtick](https://backtickjs.com/docs/thinking-in-backtick), [Scripts in depth](https://backtickjs.com/docs/scripts-in-depth)
@@ -18,6 +18,7 @@ export interface ClientScript {
18
18
  readonly "@backtickjs": "ClientScript";
19
19
  readonly module: ClientModule;
20
20
  readonly args: readonly Spliceable[];
21
+ readonly once?: true;
21
22
  }
22
23
  export declare function isClientScript(value: unknown): value is ClientScript;
23
24
  export declare function create(module: ClientModule, args: readonly Spliceable[]): ClientScript;
@@ -1 +1 @@
1
- {"version":3,"file":"ClientScript.js","sourceRoot":"","sources":["../src/ClientScript.ts"],"names":[],"mappings":"AAsCA,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,aAAa,IAAI,KAAK;QACtB,KAAK,CAAC,aAAa,CAAC,KAAK,cAAc,CACxC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,MAAM,CACpB,MAAoB,EACpB,IAA2B;IAE3B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACzD,CAAC"}
1
+ {"version":3,"file":"ClientScript.js","sourceRoot":"","sources":["../src/ClientScript.ts"],"names":[],"mappings":"AA4CA,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,aAAa,IAAI,KAAK;QACtB,KAAK,CAAC,aAAa,CAAC,KAAK,cAAc,CACxC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,MAAM,CACpB,MAAoB,EACpB,IAA2B;IAE3B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACzD,CAAC"}
@@ -18,6 +18,8 @@ export type Spliceable = Client<unknown> | null | undefined | number | bigint |
18
18
  export type SplicesAs<T> = [T] extends [Spliceable] ? T : T extends (...args: never) => unknown ? Spliceable : {
19
19
  readonly [K in keyof T]: SplicesAs<T[K]>;
20
20
  };
21
- export type Spliced<T> = T extends Client<infer U> ? U : T extends readonly (infer Item extends Spliceable)[] ? Spliced<Item>[] : T extends object ? {
21
+ export type Spliced<T> = T extends Client<infer U> ? U : T extends readonly (infer Item extends Spliceable)[] ? number extends T["length"] ? Spliced<Item>[] : {
22
+ -readonly [K in keyof T]: Spliced<T[K]>;
23
+ } : T extends object ? {
22
24
  -readonly [K in keyof T]: Spliced<T[K]>;
23
25
  } : T;
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  export { cs } from "./cs.js";
6
6
  export type { Client } from "./Client.js";
7
+ export { once } from "./once.js";
7
8
  export type { Spliceable, SplicesAs, Spliced } from "./Spliceable.js";
8
9
  export { createImport, isClientImport, type ClientImport, } from "./ClientImport.js";
9
10
  export { createJsxElement, isJsxElement, type JsxElement, type JsxElementOf, type JsxElementTypeOf, } from "./JsxElement.js";
package/dist/index.js CHANGED
@@ -2,8 +2,10 @@
2
2
  * Backtick: the `cs` tag, what a host may hand a client, and what a template
3
3
  * compiles to.
4
4
  */
5
- // The tag, and how a host language spells a script standing in for a value.
5
+ // The tag, and how a host language spells a script standing in for a value;
6
+ // a script as one value for the bundle.
6
7
  export { cs } from "./cs.js";
8
+ export { once } from "./once.js";
7
9
  export { createImport, isClientImport, } from "./ClientImport.js";
8
10
  // A drawing as a host builds one: what an adapter's JSX runtime makes, and
9
11
  // what the bundler expands.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4EAA4E;AAC5E,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAM7B,OAAO,EACL,YAAY,EACZ,cAAc,GAEf,MAAM,mBAAmB,CAAC;AAE3B,2EAA2E;AAC3E,4BAA4B;AAC5B,OAAO,EACL,gBAAgB,EAChB,YAAY,GAIb,MAAM,iBAAiB,CAAC;AAEzB,6EAA6E;AAC7E,qCAAqC;AACrC,OAAO,EACL,MAAM,EAGN,cAAc,GAEf,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4EAA4E;AAC5E,wCAAwC;AACxC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,OAAO,EACL,YAAY,EACZ,cAAc,GAEf,MAAM,mBAAmB,CAAC;AAE3B,2EAA2E;AAC3E,4BAA4B;AAC5B,OAAO,EACL,gBAAgB,EAChB,YAAY,GAIb,MAAM,iBAAiB,CAAC;AAEzB,6EAA6E;AAC7E,qCAAqC;AACrC,OAAO,EACL,MAAM,EAGN,cAAc,GAEf,MAAM,mBAAmB,CAAC"}
package/dist/once.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { Client } from "./Client.js";
2
+ /**
3
+ * A script as one value for the bundle: its code runs once, at the top of the
4
+ * bundle, and every read is that value, as a module-level `const` is in plain
5
+ * TypeScript. For what every component shares, a context or a store, which an
6
+ * expression would make again at each read. Each call of `once` is one value.
7
+ *
8
+ * Refused for a script that reads a variable of the script around it: at the
9
+ * top of the bundle, nothing encloses it.
10
+ */
11
+ export declare function once<T>(script: Client<T>): Client<T>;
package/dist/once.js ADDED
@@ -0,0 +1,31 @@
1
+ import { isClientScript, } from "./ClientScript.js";
2
+ /**
3
+ * A script as one value for the bundle: its code runs once, at the top of the
4
+ * bundle, and every read is that value, as a module-level `const` is in plain
5
+ * TypeScript. For what every component shares, a context or a store, which an
6
+ * expression would make again at each read. Each call of `once` is one value.
7
+ *
8
+ * Refused for a script that reads a variable of the script around it: at the
9
+ * top of the bundle, nothing encloses it.
10
+ */
11
+ export function once(script) {
12
+ if (!isClientScript(script)) {
13
+ throw new Error("`once` takes a script, cs`…`.");
14
+ }
15
+ const capture = script.module.params.find(isCapture);
16
+ if (capture !== undefined) {
17
+ throw new Error(`Can't make this script \`once\`: it reads \`${sourceName(capture.key)}\` ` +
18
+ "from the script around it, and a `once` value is made at the top " +
19
+ "of the bundle, outside every script.");
20
+ }
21
+ const made = { ...script, once: true };
22
+ return made;
23
+ }
24
+ function isCapture(param) {
25
+ return param.kind === "capture";
26
+ }
27
+ // A binding key is `<name>$<fileHash>$<n>`.
28
+ function sourceName(key) {
29
+ return key.replace(/\$[0-9a-z]+\$\d+$/, "");
30
+ }
31
+ //# sourceMappingURL=once.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"once.js","sourceRoot":"","sources":["../src/once.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,cAAc,GAEf,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;GAQG;AACH,MAAM,UAAU,IAAI,CAAI,MAAiB;IACvC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACrD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,+CAA+C,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK;YACzE,mEAAmE;YACnE,sCAAsC,CACzC,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAiB,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACrD,OAAO,IAA4B,CAAC;AACtC,CAAC;AAED,SAAS,SAAS,CAAC,KAAY;IAC7B,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;AAClC,CAAC;AAED,4CAA4C;AAC5C,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;AAC9C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtickjs/core",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "The cs tag for Backtick client scripts, and the types for the values that cross from server to client",
5
5
  "keywords": [
6
6
  "backtick",
@@ -28,12 +28,18 @@ export interface ClientModule {
28
28
  readonly kind: "function" | "expression" | "block";
29
29
  }
30
30
 
31
- // One run of a `cs`: its module, and the values that run passes it.
31
+ // One run of a `cs`: its module, and the values that run passes it. The rest
32
+ // are flags, how the bundle treats the script: each optional, absent meaning
33
+ // the plain script, so code that makes or spreads a script without knowing a
34
+ // flag reads the same.
32
35
  export interface ClientScript {
33
36
  readonly "@backtickjs": "ClientScript";
34
37
  readonly module: ClientModule;
35
38
  // one per splice, in parameter order
36
39
  readonly args: readonly Spliceable[];
40
+ // one value for the bundle, made at its top, rather than code run at each
41
+ // read: see `once`
42
+ readonly once?: true;
37
43
  }
38
44
 
39
45
  export function isClientScript(value: unknown): value is ClientScript {
package/src/Spliceable.ts CHANGED
@@ -32,14 +32,18 @@ export type SplicesAs<T> = [T] extends [Spliceable]
32
32
 
33
33
  // What a spliceable becomes on the client:
34
34
  // Client<U> -> U
35
- // T[] -> Spliced<T>[]
35
+ // T[], [A, B] -> Spliced<T>[], [Spliced<A>, Spliced<B>]
36
36
  // { k: T } -> { k: Spliced<T> }
37
37
  // primitives -> unchanged
38
+ // A tuple maps homomorphically, which keeps it a tuple; any other array stays
39
+ // `Spliced<Item>[]`, which TypeScript defers, so `Spliceable[]` doesn't recurse.
38
40
  export type Spliced<T> =
39
41
  T extends Client<infer U>
40
42
  ? U
41
43
  : T extends readonly (infer Item extends Spliceable)[]
42
- ? Spliced<Item>[]
44
+ ? number extends T["length"]
45
+ ? Spliced<Item>[]
46
+ : { -readonly [K in keyof T]: Spliced<T[K]> }
43
47
  : T extends object
44
48
  ? { -readonly [K in keyof T]: Spliced<T[K]> }
45
49
  : T;
package/src/index.ts CHANGED
@@ -3,9 +3,11 @@
3
3
  * compiles to.
4
4
  */
5
5
 
6
- // The tag, and how a host language spells a script standing in for a value.
6
+ // The tag, and how a host language spells a script standing in for a value;
7
+ // a script as one value for the bundle.
7
8
  export { cs } from "./cs.js";
8
9
  export type { Client } from "./Client.js";
10
+ export { once } from "./once.js";
9
11
 
10
12
  // What a host may hand a client: data, a script, or an export of a module the
11
13
  // client provides.
package/src/once.ts ADDED
@@ -0,0 +1,40 @@
1
+ import type { Client } from "./Client.js";
2
+ import {
3
+ type ClientScript,
4
+ isClientScript,
5
+ type Param,
6
+ } from "./ClientScript.js";
7
+
8
+ /**
9
+ * A script as one value for the bundle: its code runs once, at the top of the
10
+ * bundle, and every read is that value, as a module-level `const` is in plain
11
+ * TypeScript. For what every component shares, a context or a store, which an
12
+ * expression would make again at each read. Each call of `once` is one value.
13
+ *
14
+ * Refused for a script that reads a variable of the script around it: at the
15
+ * top of the bundle, nothing encloses it.
16
+ */
17
+ export function once<T>(script: Client<T>): Client<T> {
18
+ if (!isClientScript(script)) {
19
+ throw new Error("`once` takes a script, cs`…`.");
20
+ }
21
+ const capture = script.module.params.find(isCapture);
22
+ if (capture !== undefined) {
23
+ throw new Error(
24
+ `Can't make this script \`once\`: it reads \`${sourceName(capture.key)}\` ` +
25
+ "from the script around it, and a `once` value is made at the top " +
26
+ "of the bundle, outside every script.",
27
+ );
28
+ }
29
+ const made: ClientScript = { ...script, once: true };
30
+ return made as unknown as Client<T>;
31
+ }
32
+
33
+ function isCapture(param: Param): param is Extract<Param, { kind: "capture" }> {
34
+ return param.kind === "capture";
35
+ }
36
+
37
+ // A binding key is `<name>$<fileHash>$<n>`.
38
+ function sourceName(key: string): string {
39
+ return key.replace(/\$[0-9a-z]+\$\d+$/, "");
40
+ }