@meteor-vite/plugin-zodern-relay 1.0.1 → 1.0.2

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
@@ -4,10 +4,6 @@ This is a Vite compatability package for
4
4
  [`zodern:relay`](https://github.com/zodern/meteor-relay#readme) - type safe
5
5
  [Meteor](https://meteor.com/) methods and publications.
6
6
 
7
- > [!IMPORTANT]
8
- > This plugin is not yet fully complete. Methods and publications imported by your client will not be omitted from
9
- > your client bundle like it would when using the `@zodern/babel-plugin-meteor-relay` plugin.
10
-
11
7
  This plugin acts as partial replacement for the Babel `@zodern/babel-plugin-meteor-relay` plugin required by
12
8
  `zodern:relay`. You still need the Babel plugin as it might still be required on the server.
13
9
 
@@ -18,7 +14,7 @@ npm i -D @meteor-vite/plugin-zodern-relay
18
14
  ```
19
15
 
20
16
  ## Configuration
21
- Add the plugin to your Vite config and you're all set. There are no configuration options.
17
+ Add the plugin to your Vite config and you're all set. If your methods and publications reside outside of `imports/api/<methods|publications>`, specify those paths when calling the plugin.
22
18
  ```ts
23
19
  // vite.config.ts
24
20
  import zodernRelay from '@meteor-vite/plugin-zodern-relay';
@@ -29,7 +25,21 @@ export default defineConfig({
29
25
  meteor({
30
26
  clientEntry: '...',
31
27
  }),
32
- zodernRelay(),
28
+ zodernRelay({
29
+ directories: {
30
+ /**
31
+ * Path to directories where your zodern:relay methods live
32
+ * @default ['./imports/methods']
33
+ */
34
+ methods: ['./imports/methods'],
35
+
36
+ /**
37
+ * Path to the directories where your zodern:relay publications live.
38
+ * @default ['./imports/publications']
39
+ */
40
+ publications: ['./imports/publications'],
41
+ }
42
+ }),
33
43
  ]
34
44
  })
35
45
  ```
package/dist/Plugin.d.mts CHANGED
@@ -1,5 +1,19 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
- declare function zodernRelay(): Promise<Plugin>;
3
+ declare function zodernRelay(options?: Options): Promise<Plugin>;
4
+ interface Options {
5
+ directories?: {
6
+ /**
7
+ * Path to directories where your zodern:relay methods live
8
+ * @default ['./imports/methods']
9
+ */
10
+ methods?: string[];
11
+ /**
12
+ * Path to the directories where your zodern:relay publications live.
13
+ * @default ['./imports/publications']
14
+ */
15
+ publications?: string[];
16
+ };
17
+ }
4
18
 
5
- export { zodernRelay as default };
19
+ export { type Options, zodernRelay as default };
package/dist/Plugin.d.ts CHANGED
@@ -1,5 +1,19 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
- declare function zodernRelay(): Promise<Plugin>;
3
+ declare function zodernRelay(options?: Options): Promise<Plugin>;
4
+ interface Options {
5
+ directories?: {
6
+ /**
7
+ * Path to directories where your zodern:relay methods live
8
+ * @default ['./imports/methods']
9
+ */
10
+ methods?: string[];
11
+ /**
12
+ * Path to the directories where your zodern:relay publications live.
13
+ * @default ['./imports/publications']
14
+ */
15
+ publications?: string[];
16
+ };
17
+ }
4
18
 
5
- export { zodernRelay as default };
19
+ export { type Options, zodernRelay as default };
package/dist/Plugin.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/Plugin.ts
@@ -23,22 +33,59 @@ __export(Plugin_exports, {
23
33
  default: () => zodernRelay
24
34
  });
25
35
  module.exports = __toCommonJS(Plugin_exports);
26
- var sourceModule = "meteor/zodern:relay";
27
- var stubModule = "@meteor-vite/zodern-relay/stubs/relay-client";
28
- async function zodernRelay() {
36
+ var import_core = require("@babel/core");
37
+ var import_fs = __toESM(require("fs"));
38
+ var import_path = __toESM(require("path"));
39
+ var cwd = process.cwd();
40
+ async function zodernRelay(options) {
41
+ const config = {
42
+ directories: {
43
+ methods: options?.directories?.methods || ["./imports/methods"],
44
+ publications: options?.directories?.publications || ["./imports/publications"]
45
+ }
46
+ };
47
+ const directories = [
48
+ ...config.directories.methods.map((path) => ["methods", import_path.default.relative(cwd, path)]),
49
+ ...config.directories.publications.map((path) => ["publications", import_path.default.relative(cwd, path)])
50
+ ];
51
+ function resolveRelay(id) {
52
+ const relativePath = import_path.default.relative(cwd, id);
53
+ for (const [type, directory] of directories) {
54
+ if (!relativePath.startsWith(directory)) {
55
+ continue;
56
+ }
57
+ return {
58
+ id,
59
+ type,
60
+ relativePath
61
+ };
62
+ }
63
+ }
29
64
  return {
30
65
  name: "zodern-relay",
31
- resolveId(id) {
32
- if (!id.startsWith(sourceModule)) {
66
+ async load(filename) {
67
+ const relay = resolveRelay(filename || "");
68
+ if (!relay) {
33
69
  return;
34
70
  }
35
- return `\0${id}`;
36
- },
37
- load(id) {
38
- if (!id.startsWith(`\0${sourceModule}`)) {
71
+ const code = import_fs.default.readFileSync(filename, "utf-8");
72
+ const transform = await (0, import_core.transformAsync)(code, {
73
+ configFile: false,
74
+ babelrc: false,
75
+ filename,
76
+ plugins: ["@zodern/babel-plugin-meteor-relay"],
77
+ caller: {
78
+ name: "@meteor-vite/plugin-zodern-relay",
79
+ // @ts-expect-error No type definition for this, but it's required by the Babel plugin.
80
+ arch: "web.browser.vite"
81
+ }
82
+ });
83
+ if (!transform) {
39
84
  return;
40
85
  }
41
- return `export * from ${JSON.stringify(stubModule)}`;
86
+ return {
87
+ code: transform.code ?? ""
88
+ };
42
89
  }
43
90
  };
44
91
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Plugin.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\nconst sourceModule = 'meteor/zodern:relay';\nconst stubModule = '@meteor-vite/zodern-relay/stubs/relay-client';\n\nexport default async function zodernRelay(): Promise<Plugin> {\n return {\n name: 'zodern-relay',\n resolveId(id) {\n if (!id.startsWith(sourceModule)) {\n return;\n }\n return `\\0${id}`;\n },\n load(id) {\n if (!id.startsWith(`\\0${sourceModule}`)) {\n return;\n }\n \n // language=typescript\n return `export * from ${JSON.stringify(stubModule)}`;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,IAAM,eAAe;AACrB,IAAM,aAAa;AAEnB,eAAO,cAAsD;AACzD,SAAO;AAAA,IACH,MAAM;AAAA,IACN,UAAU,IAAI;AACV,UAAI,CAAC,GAAG,WAAW,YAAY,GAAG;AAC9B;AAAA,MACJ;AACA,aAAO,KAAK,EAAE;AAAA,IAClB;AAAA,IACA,KAAK,IAAI;AACL,UAAI,CAAC,GAAG,WAAW,KAAK,YAAY,EAAE,GAAG;AACrC;AAAA,MACJ;AAGA,aAAO,iBAAiB,KAAK,UAAU,UAAU,CAAC;AAAA,IACtD;AAAA,EACJ;AACJ;","names":[]}
1
+ {"version":3,"sources":["../src/Plugin.ts"],"sourcesContent":["import { transformAsync } from '@babel/core';\nimport FS from 'fs';\nimport Path from 'path';\nimport { type Plugin } from 'vite';\n\nconst cwd = process.cwd();\n\nexport default async function zodernRelay(options?: Options): Promise<Plugin> {\n const config = {\n directories: {\n methods: options?.directories?.methods || ['./imports/methods'],\n publications: options?.directories?.publications || ['./imports/publications'],\n }\n } satisfies Options;\n \n const directories = [\n ...config.directories.methods.map((path) => ['methods', Path.relative(cwd, path)]),\n ...config.directories.publications.map((path) => ['publications', Path.relative(cwd, path)])\n ] as [RelayInfo['type'], string][];\n \n function resolveRelay(id: string): RelayInfo | undefined {\n const relativePath = Path.relative(cwd, id);\n for (const [type, directory] of directories) {\n if (!relativePath.startsWith(directory)) {\n continue;\n }\n return {\n id,\n type,\n relativePath,\n }\n }\n }\n \n return {\n name: 'zodern-relay',\n async load(filename) {\n const relay = resolveRelay(filename || '');\n if (!relay) {\n return;\n }\n const code = FS.readFileSync(filename, 'utf-8');\n const transform = await transformAsync(code, {\n configFile: false,\n babelrc: false,\n filename,\n plugins: ['@zodern/babel-plugin-meteor-relay'],\n caller: {\n name: '@meteor-vite/plugin-zodern-relay',\n \n // @ts-expect-error No type definition for this, but it's required by the Babel plugin.\n arch: 'web.browser.vite',\n }\n });\n \n if (!transform) {\n return;\n }\n \n return {\n code: transform.code ?? '',\n }\n }\n }\n \n \n}\nexport interface Options {\n directories?: {\n /**\n * Path to directories where your zodern:relay methods live\n * @default ['./imports/methods']\n */\n methods?: string[],\n \n /**\n * Path to the directories where your zodern:relay publications live.\n * @default ['./imports/publications']\n */\n publications?: string[],\n }\n}\n\ntype RelayInfo = {\n type: 'methods' | 'publications';\n id: string;\n relativePath: string;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAA+B;AAC/B,gBAAe;AACf,kBAAiB;AAGjB,IAAM,MAAM,QAAQ,IAAI;AAExB,eAAO,YAAmC,SAAoC;AAC1E,QAAM,SAAS;AAAA,IACX,aAAa;AAAA,MACT,SAAS,SAAS,aAAa,WAAW,CAAC,mBAAmB;AAAA,MAC9D,cAAc,SAAS,aAAa,gBAAgB,CAAC,wBAAwB;AAAA,IACjF;AAAA,EACJ;AAEA,QAAM,cAAc;AAAA,IAChB,GAAG,OAAO,YAAY,QAAQ,IAAI,CAAC,SAAS,CAAC,WAAW,YAAAA,QAAK,SAAS,KAAK,IAAI,CAAC,CAAC;AAAA,IACjF,GAAG,OAAO,YAAY,aAAa,IAAI,CAAC,SAAS,CAAC,gBAAgB,YAAAA,QAAK,SAAS,KAAK,IAAI,CAAC,CAAC;AAAA,EAC/F;AAEA,WAAS,aAAa,IAAmC;AACrD,UAAM,eAAe,YAAAA,QAAK,SAAS,KAAK,EAAE;AAC1C,eAAW,CAAC,MAAM,SAAS,KAAK,aAAa;AACzC,UAAI,CAAC,aAAa,WAAW,SAAS,GAAG;AACrC;AAAA,MACJ;AACA,aAAO;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAEA,SAAO;AAAA,IACH,MAAM;AAAA,IACN,MAAM,KAAK,UAAU;AACjB,YAAM,QAAQ,aAAa,YAAY,EAAE;AACzC,UAAI,CAAC,OAAO;AACR;AAAA,MACJ;AACA,YAAM,OAAO,UAAAC,QAAG,aAAa,UAAU,OAAO;AAC9C,YAAM,YAAY,UAAM,4BAAe,MAAM;AAAA,QACzC,YAAY;AAAA,QACZ,SAAS;AAAA,QACT;AAAA,QACA,SAAS,CAAC,mCAAmC;AAAA,QAC7C,QAAQ;AAAA,UACJ,MAAM;AAAA;AAAA,UAGN,MAAM;AAAA,QACV;AAAA,MACJ,CAAC;AAED,UAAI,CAAC,WAAW;AACZ;AAAA,MACJ;AAEA,aAAO;AAAA,QACH,MAAM,UAAU,QAAQ;AAAA,MAC5B;AAAA,IACJ;AAAA,EACJ;AAGJ;","names":["Path","FS"]}
package/dist/Plugin.mjs CHANGED
@@ -1,20 +1,57 @@
1
1
  // src/Plugin.ts
2
- var sourceModule = "meteor/zodern:relay";
3
- var stubModule = "@meteor-vite/zodern-relay/stubs/relay-client";
4
- async function zodernRelay() {
2
+ import { transformAsync } from "@babel/core";
3
+ import FS from "fs";
4
+ import Path from "path";
5
+ var cwd = process.cwd();
6
+ async function zodernRelay(options) {
7
+ const config = {
8
+ directories: {
9
+ methods: options?.directories?.methods || ["./imports/methods"],
10
+ publications: options?.directories?.publications || ["./imports/publications"]
11
+ }
12
+ };
13
+ const directories = [
14
+ ...config.directories.methods.map((path) => ["methods", Path.relative(cwd, path)]),
15
+ ...config.directories.publications.map((path) => ["publications", Path.relative(cwd, path)])
16
+ ];
17
+ function resolveRelay(id) {
18
+ const relativePath = Path.relative(cwd, id);
19
+ for (const [type, directory] of directories) {
20
+ if (!relativePath.startsWith(directory)) {
21
+ continue;
22
+ }
23
+ return {
24
+ id,
25
+ type,
26
+ relativePath
27
+ };
28
+ }
29
+ }
5
30
  return {
6
31
  name: "zodern-relay",
7
- resolveId(id) {
8
- if (!id.startsWith(sourceModule)) {
32
+ async load(filename) {
33
+ const relay = resolveRelay(filename || "");
34
+ if (!relay) {
9
35
  return;
10
36
  }
11
- return `\0${id}`;
12
- },
13
- load(id) {
14
- if (!id.startsWith(`\0${sourceModule}`)) {
37
+ const code = FS.readFileSync(filename, "utf-8");
38
+ const transform = await transformAsync(code, {
39
+ configFile: false,
40
+ babelrc: false,
41
+ filename,
42
+ plugins: ["@zodern/babel-plugin-meteor-relay"],
43
+ caller: {
44
+ name: "@meteor-vite/plugin-zodern-relay",
45
+ // @ts-expect-error No type definition for this, but it's required by the Babel plugin.
46
+ arch: "web.browser.vite"
47
+ }
48
+ });
49
+ if (!transform) {
15
50
  return;
16
51
  }
17
- return `export * from ${JSON.stringify(stubModule)}`;
52
+ return {
53
+ code: transform.code ?? ""
54
+ };
18
55
  }
19
56
  };
20
57
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Plugin.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\nconst sourceModule = 'meteor/zodern:relay';\nconst stubModule = '@meteor-vite/zodern-relay/stubs/relay-client';\n\nexport default async function zodernRelay(): Promise<Plugin> {\n return {\n name: 'zodern-relay',\n resolveId(id) {\n if (!id.startsWith(sourceModule)) {\n return;\n }\n return `\\0${id}`;\n },\n load(id) {\n if (!id.startsWith(`\\0${sourceModule}`)) {\n return;\n }\n \n // language=typescript\n return `export * from ${JSON.stringify(stubModule)}`;\n }\n }\n}\n"],"mappings":";AAEA,IAAM,eAAe;AACrB,IAAM,aAAa;AAEnB,eAAO,cAAsD;AACzD,SAAO;AAAA,IACH,MAAM;AAAA,IACN,UAAU,IAAI;AACV,UAAI,CAAC,GAAG,WAAW,YAAY,GAAG;AAC9B;AAAA,MACJ;AACA,aAAO,KAAK,EAAE;AAAA,IAClB;AAAA,IACA,KAAK,IAAI;AACL,UAAI,CAAC,GAAG,WAAW,KAAK,YAAY,EAAE,GAAG;AACrC;AAAA,MACJ;AAGA,aAAO,iBAAiB,KAAK,UAAU,UAAU,CAAC;AAAA,IACtD;AAAA,EACJ;AACJ;","names":[]}
1
+ {"version":3,"sources":["../src/Plugin.ts"],"sourcesContent":["import { transformAsync } from '@babel/core';\nimport FS from 'fs';\nimport Path from 'path';\nimport { type Plugin } from 'vite';\n\nconst cwd = process.cwd();\n\nexport default async function zodernRelay(options?: Options): Promise<Plugin> {\n const config = {\n directories: {\n methods: options?.directories?.methods || ['./imports/methods'],\n publications: options?.directories?.publications || ['./imports/publications'],\n }\n } satisfies Options;\n \n const directories = [\n ...config.directories.methods.map((path) => ['methods', Path.relative(cwd, path)]),\n ...config.directories.publications.map((path) => ['publications', Path.relative(cwd, path)])\n ] as [RelayInfo['type'], string][];\n \n function resolveRelay(id: string): RelayInfo | undefined {\n const relativePath = Path.relative(cwd, id);\n for (const [type, directory] of directories) {\n if (!relativePath.startsWith(directory)) {\n continue;\n }\n return {\n id,\n type,\n relativePath,\n }\n }\n }\n \n return {\n name: 'zodern-relay',\n async load(filename) {\n const relay = resolveRelay(filename || '');\n if (!relay) {\n return;\n }\n const code = FS.readFileSync(filename, 'utf-8');\n const transform = await transformAsync(code, {\n configFile: false,\n babelrc: false,\n filename,\n plugins: ['@zodern/babel-plugin-meteor-relay'],\n caller: {\n name: '@meteor-vite/plugin-zodern-relay',\n \n // @ts-expect-error No type definition for this, but it's required by the Babel plugin.\n arch: 'web.browser.vite',\n }\n });\n \n if (!transform) {\n return;\n }\n \n return {\n code: transform.code ?? '',\n }\n }\n }\n \n \n}\nexport interface Options {\n directories?: {\n /**\n * Path to directories where your zodern:relay methods live\n * @default ['./imports/methods']\n */\n methods?: string[],\n \n /**\n * Path to the directories where your zodern:relay publications live.\n * @default ['./imports/publications']\n */\n publications?: string[],\n }\n}\n\ntype RelayInfo = {\n type: 'methods' | 'publications';\n id: string;\n relativePath: string;\n};\n"],"mappings":";AAAA,SAAS,sBAAsB;AAC/B,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,IAAM,MAAM,QAAQ,IAAI;AAExB,eAAO,YAAmC,SAAoC;AAC1E,QAAM,SAAS;AAAA,IACX,aAAa;AAAA,MACT,SAAS,SAAS,aAAa,WAAW,CAAC,mBAAmB;AAAA,MAC9D,cAAc,SAAS,aAAa,gBAAgB,CAAC,wBAAwB;AAAA,IACjF;AAAA,EACJ;AAEA,QAAM,cAAc;AAAA,IAChB,GAAG,OAAO,YAAY,QAAQ,IAAI,CAAC,SAAS,CAAC,WAAW,KAAK,SAAS,KAAK,IAAI,CAAC,CAAC;AAAA,IACjF,GAAG,OAAO,YAAY,aAAa,IAAI,CAAC,SAAS,CAAC,gBAAgB,KAAK,SAAS,KAAK,IAAI,CAAC,CAAC;AAAA,EAC/F;AAEA,WAAS,aAAa,IAAmC;AACrD,UAAM,eAAe,KAAK,SAAS,KAAK,EAAE;AAC1C,eAAW,CAAC,MAAM,SAAS,KAAK,aAAa;AACzC,UAAI,CAAC,aAAa,WAAW,SAAS,GAAG;AACrC;AAAA,MACJ;AACA,aAAO;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAEA,SAAO;AAAA,IACH,MAAM;AAAA,IACN,MAAM,KAAK,UAAU;AACjB,YAAM,QAAQ,aAAa,YAAY,EAAE;AACzC,UAAI,CAAC,OAAO;AACR;AAAA,MACJ;AACA,YAAM,OAAO,GAAG,aAAa,UAAU,OAAO;AAC9C,YAAM,YAAY,MAAM,eAAe,MAAM;AAAA,QACzC,YAAY;AAAA,QACZ,SAAS;AAAA,QACT;AAAA,QACA,SAAS,CAAC,mCAAmC;AAAA,QAC7C,QAAQ;AAAA,UACJ,MAAM;AAAA;AAAA,UAGN,MAAM;AAAA,QACV;AAAA,MACJ,CAAC;AAED,UAAI,CAAC,WAAW;AACZ;AAAA,MACJ;AAEA,aAAO;AAAA,QACH,MAAM,UAAU,QAAQ;AAAA,MAC5B;AAAA,IACJ;AAAA,EACJ;AAGJ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meteor-vite/plugin-zodern-relay",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Vite compatability plugin for zodern:relay - typed Meteor methods and publications",
5
5
  "main": "dist/Plugin.js",
6
6
  "exports": {
@@ -40,8 +40,13 @@
40
40
  "url": "https://github.com/JorgenVatle/meteor-vite"
41
41
  },
42
42
  "devDependencies": {
43
+ "@types/babel__core": "^7.20.5",
43
44
  "tsup": "^8.0.2",
44
45
  "typescript": "^5.4.2",
45
46
  "vite": "^5.1.6"
47
+ },
48
+ "peerDependencies": {
49
+ "@babel/core": "^7.0.0",
50
+ "vite": ">=3.0.0"
46
51
  }
47
52
  }
package/src/Plugin.ts CHANGED
@@ -1,24 +1,88 @@
1
- import type { Plugin } from 'vite';
1
+ import { transformAsync } from '@babel/core';
2
+ import FS from 'fs';
3
+ import Path from 'path';
4
+ import { type Plugin } from 'vite';
2
5
 
3
- const sourceModule = 'meteor/zodern:relay';
4
- const stubModule = '@meteor-vite/zodern-relay/stubs/relay-client';
6
+ const cwd = process.cwd();
5
7
 
6
- export default async function zodernRelay(): Promise<Plugin> {
8
+ export default async function zodernRelay(options?: Options): Promise<Plugin> {
9
+ const config = {
10
+ directories: {
11
+ methods: options?.directories?.methods || ['./imports/methods'],
12
+ publications: options?.directories?.publications || ['./imports/publications'],
13
+ }
14
+ } satisfies Options;
15
+
16
+ const directories = [
17
+ ...config.directories.methods.map((path) => ['methods', Path.relative(cwd, path)]),
18
+ ...config.directories.publications.map((path) => ['publications', Path.relative(cwd, path)])
19
+ ] as [RelayInfo['type'], string][];
20
+
21
+ function resolveRelay(id: string): RelayInfo | undefined {
22
+ const relativePath = Path.relative(cwd, id);
23
+ for (const [type, directory] of directories) {
24
+ if (!relativePath.startsWith(directory)) {
25
+ continue;
26
+ }
27
+ return {
28
+ id,
29
+ type,
30
+ relativePath,
31
+ }
32
+ }
33
+ }
34
+
7
35
  return {
8
36
  name: 'zodern-relay',
9
- resolveId(id) {
10
- if (!id.startsWith(sourceModule)) {
37
+ async load(filename) {
38
+ const relay = resolveRelay(filename || '');
39
+ if (!relay) {
11
40
  return;
12
41
  }
13
- return `\0${id}`;
14
- },
15
- load(id) {
16
- if (!id.startsWith(`\0${sourceModule}`)) {
42
+ const code = FS.readFileSync(filename, 'utf-8');
43
+ const transform = await transformAsync(code, {
44
+ configFile: false,
45
+ babelrc: false,
46
+ filename,
47
+ plugins: ['@zodern/babel-plugin-meteor-relay'],
48
+ caller: {
49
+ name: '@meteor-vite/plugin-zodern-relay',
50
+
51
+ // @ts-expect-error No type definition for this, but it's required by the Babel plugin.
52
+ arch: 'web.browser.vite',
53
+ }
54
+ });
55
+
56
+ if (!transform) {
17
57
  return;
18
58
  }
19
59
 
20
- // language=typescript
21
- return `export * from ${JSON.stringify(stubModule)}`;
60
+ return {
61
+ code: transform.code ?? '',
62
+ }
22
63
  }
23
64
  }
65
+
66
+
24
67
  }
68
+ export interface Options {
69
+ directories?: {
70
+ /**
71
+ * Path to directories where your zodern:relay methods live
72
+ * @default ['./imports/methods']
73
+ */
74
+ methods?: string[],
75
+
76
+ /**
77
+ * Path to the directories where your zodern:relay publications live.
78
+ * @default ['./imports/publications']
79
+ */
80
+ publications?: string[],
81
+ }
82
+ }
83
+
84
+ type RelayInfo = {
85
+ type: 'methods' | 'publications';
86
+ id: string;
87
+ relativePath: string;
88
+ };
@@ -0,0 +1,445 @@
1
+ const { createHash } = require('crypto');
2
+ const path = require('path');
3
+
4
+ // If has a MemberExpression, returns the first call expression in its callee
5
+ // Otherwise, returns the call expression
6
+ function getFirstCallExpr(call) {
7
+ if (call.node.callee.type !== 'MemberExpression') {
8
+ return call;
9
+ }
10
+
11
+
12
+ if (
13
+ call.node.callee.object.type !== 'CallExpression'
14
+ ) {
15
+ return;
16
+ }
17
+
18
+ return call.get('callee.object');
19
+ }
20
+
21
+ module.exports = function (api) {
22
+ let t = api.types;
23
+
24
+ let caller;
25
+ api.caller(function (c) {
26
+ caller = c;
27
+ });
28
+
29
+ function createExport(exportName, callee, name, stub) {
30
+ let args = [
31
+ t.StringLiteral(name)
32
+ ];
33
+
34
+ if (stub) {
35
+ if (stub.type === 'ObjectMethod') {
36
+ stub = t.FunctionExpression(
37
+ null,
38
+ stub.node.params || [],
39
+ stub.node.body,
40
+ stub.node.generator,
41
+ stub.node.async
42
+ )
43
+ } else if (stub.type === 'ObjectProperty') {
44
+ stub = stub.node.value;
45
+ }
46
+ args.push(stub);
47
+ }
48
+
49
+ const declaration = t.CallExpression(
50
+ t.Identifier(callee),
51
+ args
52
+ );
53
+
54
+ if (exportName === null) {
55
+ return t.ExportDefaultDeclaration(
56
+ declaration
57
+ );
58
+ }
59
+
60
+ return t.ExportNamedDeclaration(
61
+ t.VariableDeclaration(
62
+ 'const',
63
+ [t.VariableDeclarator(
64
+ t.Identifier(exportName),
65
+ declaration
66
+ )]
67
+ )
68
+ )
69
+ }
70
+
71
+ function getOrAddName(args, { exportName, filePath, isPub }) {
72
+ if (args[0].type !== 'ObjectExpression') {
73
+ return;
74
+ }
75
+
76
+ let obj = args[0];
77
+ let nameProperty = obj.properties.find((property) => {
78
+ if (property.key.type !== 'Identifier') {
79
+ return false;
80
+ }
81
+
82
+ if (property.key.name !== 'name') {
83
+ return false;
84
+ }
85
+
86
+ return property.value.type === 'StringLiteral';
87
+ });
88
+
89
+ if (nameProperty) {
90
+ return nameProperty.value.value;
91
+ }
92
+
93
+ let fileHash = 'M' + createHash('sha256')
94
+ .update(filePath)
95
+ .digest('hex')
96
+ .substring(0, 5);
97
+
98
+ let name = exportName;
99
+
100
+ if (name === null) {
101
+ let baseName = path.basename(filePath);
102
+ let lastDotIndex = baseName.lastIndexOf('.');
103
+ name = baseName.substring(0, lastDotIndex);
104
+ } else if (isPub && name.startsWith('subscribe')) {
105
+ name = exportName.substring('subscribe'.length);
106
+
107
+ if (name[0] !== name[0].toLowerCase()) {
108
+ name = `${name[0].toLowerCase()}${name.substring(1)}`
109
+ }
110
+ }
111
+
112
+ name += fileHash;
113
+
114
+ obj.properties.push(t.ObjectProperty(
115
+ t.Identifier('name'),
116
+ t.StringLiteral(name)
117
+ ));
118
+
119
+ return name;
120
+ }
121
+
122
+ // TODO: args should be a path instead of node
123
+ function findStubPropertyIndex(args) {
124
+ let obj = args[0];
125
+ let stubPropIndex = obj.properties.findIndex((property) => {
126
+ if (property.key.type !== 'Identifier') {
127
+ return false;
128
+ }
129
+
130
+ if (property.key.name !== 'stub') {
131
+ return false;
132
+ }
133
+
134
+ return true;
135
+ });
136
+ let stub = obj.properties[stubPropIndex];
137
+
138
+ if (!stub) {
139
+ return -1;
140
+ }
141
+
142
+ if (
143
+ stub.type === 'ObjectMethod' ||
144
+ stub.value.type === 'FunctionExpression' ||
145
+ stub.value.type === 'ArrowFunctionExpression'
146
+ ) {
147
+ return stubPropIndex;
148
+ }
149
+
150
+
151
+ if (stub.value.type !== 'BooleanLiteral') {
152
+ return -1;
153
+ }
154
+
155
+ if (stub.value.value !== true) {
156
+ return -1;
157
+ }
158
+
159
+ // stub is set to true - use the run function
160
+ return obj.properties.findIndex((property) => {
161
+ if (property.key.type !== 'Identifier') {
162
+ return false;
163
+ }
164
+
165
+ if (property.key.name !== 'run') {
166
+ return false;
167
+ }
168
+
169
+ return true;
170
+ });
171
+ }
172
+
173
+ let canHaveMethods = false;
174
+ let canHavePublications = false;
175
+ let createMethodName = null;
176
+ let createPublicationName = null;
177
+ let methods = [];
178
+ let publications = [];
179
+ let isServer = false;
180
+ let filePath = ''
181
+ let imports = Object.create(null);
182
+
183
+ return {
184
+ visitor: {
185
+ Program: {
186
+ enter(_, state) {
187
+ createMethodName = null;
188
+ createPublicationName = null;
189
+ methods = [];
190
+ publications = [];
191
+
192
+ let relPath = path
193
+ .relative(state.cwd, state.filename)
194
+ .split(path.sep)
195
+ .join(path.posix.sep);
196
+ filePath = relPath;
197
+
198
+ canHaveMethods = relPath.includes('/methods/') || relPath.startsWith('methods/');
199
+ canHavePublications = relPath.includes('/publications/') || relPath.startsWith('publications/');
200
+
201
+ isServer = caller.arch.startsWith('os.');
202
+
203
+ if (!canHaveMethods && !canHavePublications) {
204
+ return;
205
+ }
206
+ },
207
+ exit(path) {
208
+ if (isServer || !canHaveMethods && !canHavePublications) {
209
+ return;
210
+ }
211
+
212
+ if (methods.length === 0 && publications.length === 0) {
213
+ path.node.body = [];
214
+ return;
215
+ }
216
+
217
+ let body = [];
218
+
219
+ let importSpecifiers = [];
220
+ if (methods.length > 0) {
221
+ importSpecifiers.push(
222
+ t.ImportSpecifier(t.Identifier('_createClientMethod'), t.Identifier('_createClientMethod'))
223
+ );
224
+ }
225
+ if (publications.length > 0) {
226
+ importSpecifiers.push(
227
+ t.ImportSpecifier(t.Identifier('_createClientPublication'), t.Identifier('_createClientPublication'))
228
+ )
229
+ }
230
+
231
+ let importDecl = t.ImportDeclaration(
232
+ importSpecifiers,
233
+ t.StringLiteral('meteor/zodern:relay/client'),
234
+ );
235
+
236
+ body.push(importDecl);
237
+
238
+ methods.forEach(method => {
239
+ if (method.stub) {
240
+ let stubBody = method.stub.get('body').node ?
241
+ method.stub.get('body') : method.stub.get('value.body');
242
+ stubBody.traverse({
243
+ ReferencedIdentifier(subPath) {
244
+ if (stubBody.scope.hasOwnBinding(subPath.node.name)) {
245
+ return;
246
+ }
247
+
248
+ if (subPath.node.name in imports) {
249
+ let importDesc = imports[subPath.node.name];
250
+
251
+ let specifier;
252
+ if (importDesc.type === 'ImportDefaultSpecifier') {
253
+ specifier = t.ImportDefaultSpecifier(t.Identifier(subPath.node.name));
254
+ } else if (importDesc.type === 'ImportNamespaceSpecifier') {
255
+ specifier = t.ImportNamespaceSpecifier(t.Identifier(subPath.node.name));
256
+ } else {
257
+ specifier = t.ImportSpecifier(
258
+ t.Identifier(importDesc.importName),
259
+ t.Identifier(subPath.node.name)
260
+ );
261
+ }
262
+
263
+ // TODO: we should preserve the original order of the imports
264
+ body.push(t.ImportDeclaration(
265
+ [ specifier ],
266
+ t.StringLiteral(importDesc.source)
267
+ ));
268
+ }
269
+ }
270
+ });
271
+ }
272
+
273
+ body.push(
274
+ createExport(method.export, '_createClientMethod', method.name, method.stub)
275
+ );
276
+ });
277
+
278
+ publications.forEach(publication => {
279
+ body.push(
280
+ createExport(publication.export, '_createClientPublication', publication.name)
281
+ );
282
+ });
283
+
284
+ path.node.body = body;
285
+
286
+ return;
287
+ },
288
+ },
289
+ ImportDeclaration(path) {
290
+ path.node.specifiers.forEach(specifier => {
291
+ let type = specifier.type;
292
+ let hasImportName = type !== 'ImportDefaultSpecifier' &&
293
+ type !== 'ImportNamespaceSpecifier'
294
+ imports[specifier.local.name] = {
295
+ type,
296
+ importName: hasImportName ?
297
+ specifier.imported.name :
298
+ null,
299
+ source: path.node.source.value
300
+ };
301
+
302
+ if (!hasImportName) {
303
+ return;
304
+ }
305
+
306
+ if (canHaveMethods && specifier.imported.name === 'createMethod') {
307
+ createMethodName = specifier.local.name;
308
+ }
309
+ if (canHavePublications && specifier.imported.name === 'createPublication') {
310
+ createPublicationName = specifier.local.name;
311
+ }
312
+ });
313
+ },
314
+ ExportDefaultDeclaration(path) {
315
+ if (path.node.declaration.type !== 'CallExpression') {
316
+ return;
317
+ }
318
+
319
+ let call = getFirstCallExpr(path.get('declaration'));
320
+
321
+ if (!call) {
322
+ return;
323
+ }
324
+
325
+ if (
326
+ call.node.callee.name === createMethodName
327
+ ) {
328
+ let name = getOrAddName(call.node.arguments, {
329
+ exportName: null,
330
+ filePath,
331
+ isPub: false
332
+ });
333
+ if (name === undefined) {
334
+ throw new Error('Unable to find name for createMethod');
335
+ }
336
+ let stubPropIndex = findStubPropertyIndex(call.node.arguments);
337
+ let stub;
338
+ if (stubPropIndex > -1) {
339
+ stub = call.get(`arguments.0.properties.${stubPropIndex}`);
340
+ }
341
+
342
+ methods.push({
343
+ name: name,
344
+ export: null,
345
+ stub
346
+ });
347
+ }
348
+
349
+ if (
350
+ call.node.callee.name === createPublicationName
351
+ ) {
352
+ let name = getOrAddName(call.node.arguments, {
353
+ exportName: null,
354
+ filePath,
355
+ isPub: true
356
+ });
357
+ if (name === undefined) {
358
+ throw new Error('Unable to find name for createMethod');
359
+ }
360
+
361
+ publications.push({
362
+ name: name,
363
+ export: null
364
+ });
365
+ }
366
+ },
367
+ ExportNamedDeclaration(path) {
368
+ let declaration = path.get('declaration');
369
+
370
+ if (
371
+ // null when the code is something like "export { h };"
372
+ !declaration.node ||
373
+ declaration.isFunctionDeclaration()
374
+ ) {
375
+ return;
376
+ }
377
+
378
+ if (declaration.type == 'ClassDeclaration') {
379
+ return;
380
+ }
381
+
382
+ if (declaration.type !== 'VariableDeclaration') {
383
+ throw new Error(`export declarations of type ${declaration.type} are not supported`);
384
+ }
385
+
386
+ declaration.get('declarations').forEach(vDeclaration => {
387
+ if (!vDeclaration.isVariableDeclarator()) {
388
+ throw new Error(`Unsupported declaration type in VariableDeclaration: ${vDeclaration.node.type}`);
389
+ }
390
+
391
+ if (!vDeclaration.get('init').isCallExpression()) {
392
+ return;
393
+ }
394
+
395
+ let call = getFirstCallExpr(vDeclaration.get('init'));
396
+
397
+ if (!call) {
398
+ return;
399
+ }
400
+
401
+ if (
402
+ call.node.callee.name === createMethodName
403
+ ) {
404
+ let name = getOrAddName(call.node.arguments, {
405
+ exportName: vDeclaration.node.id.name,
406
+ filePath,
407
+ isPub: false
408
+ });
409
+ if (name === undefined) {
410
+ throw new Error('Unable to find name for createMethod');
411
+ }
412
+ let stubPropIndex = findStubPropertyIndex(call.node.arguments);
413
+ let stub;
414
+ if (stubPropIndex > -1) {
415
+ stub = call.get(`arguments.0.properties.${stubPropIndex}`);
416
+ }
417
+ methods.push({
418
+ name: name,
419
+ export: vDeclaration.node.id.name,
420
+ stub
421
+ });
422
+ }
423
+
424
+ if (
425
+ call.node.callee.name === createPublicationName
426
+ ) {
427
+ let name = getOrAddName(call.node.arguments, {
428
+ exportName: vDeclaration.node.id.name,
429
+ filePath,
430
+ isPub: true
431
+ });
432
+ if (name === undefined) {
433
+ throw new Error('Unable to find name for createMethod');
434
+ }
435
+
436
+ publications.push({
437
+ name: name,
438
+ export: vDeclaration.node.id.name
439
+ });
440
+ }
441
+ })
442
+ }
443
+ }
444
+ };
445
+ }