oc-server 0.4.5 → 1.1.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/dist/Server.d.ts CHANGED
@@ -1,23 +1,41 @@
1
1
  import { DataContext, DataProvider } from './types';
2
+ type Prettify<T> = {
3
+ [K in keyof T]: T[K];
4
+ } & {};
2
5
  export type ServerContext<E = {
3
6
  name: string;
4
- }, P = any> = Omit<DataContext<any, E, P>, 'params' | 'action' | 'setEmptyResponse'>;
5
- export type Action<I, O, E> = (params: I, ctx: ServerContext<E>) => Promise<O> | O;
6
- type AnyAction = Action<any, any, any>;
7
- export declare class Server<E, A extends Record<string, AnyAction> = {}, InitialInput = any, InitialOutput = any> {
8
- readonly initial: Action<InitialInput, InitialOutput, E>;
7
+ }, P = any, S = any> = Omit<DataContext<any, E, P, S>, 'params' | 'action' | 'setEmptyResponse'>;
8
+ export type Action<I, O, E, P, S> = (params: I, ctx: ServerContext<E, P, S>) => Promise<O> | O;
9
+ type AnyAction = Action<any, any, any, any, any>;
10
+ type IsGeneralStringName<T> = T extends {
11
+ name: infer N;
12
+ } ? N extends string ? string extends N ? true : false : false : false;
13
+ export declare class Server<E = {
14
+ name: string;
15
+ }, P = unknown, A extends Record<string, AnyAction> = {}, InitialInput = any, InitialOutput = any, MiddlewareInput = any, MiddlewareOutput = any> {
16
+ private _middleware;
17
+ constructor();
18
+ middleware<I, O>(action: Action<I, O, E, P, unknown>): Omit<Server<E, P, A, InitialInput, InitialOutput, I, O>, P extends Record<string, unknown> ? IsGeneralStringName<E> extends false ? 'middleware' | 'definePlugins' | 'defineEnv' : 'middleware' | 'definePlugins' : IsGeneralStringName<E> extends false ? 'middleware' | 'defineEnv' : 'middleware'>;
19
+ defineEnv<Env>(): P extends Record<string, any> ? Omit<Server<Env, P, A, InitialInput, InitialOutput>, 'defineEnv' | 'definePlugins'> : Omit<Server<Env, P, A, InitialInput, InitialOutput>, 'defineEnv'>;
20
+ definePlugins<Plugins>(): IsGeneralStringName<E> extends true ? Omit<Server<E, Plugins, A, InitialInput, InitialOutput>, 'definePlugins'> : Omit<Server<E, Plugins, A, InitialInput, InitialOutput>, 'definePlugins' | 'defineEnv'>;
21
+ handler<I, O>(action: Action<I, O, E, P, MiddlewareOutput>): HandledServer<E, P, A, I, O, MiddlewareInput, MiddlewareOutput>;
22
+ }
23
+ declare class HandledServer<E, P, A extends Record<string, AnyAction> = {}, InitialInput = any, InitialOutput = any, MiddlewareInput = any, MiddlewareOutput = any> {
24
+ readonly initial: Action<InitialInput, InitialOutput, E, P, MiddlewareOutput>;
25
+ private readonly _middleware;
9
26
  readonly actions: A;
10
- constructor(initial: Action<InitialInput, InitialOutput, E>);
11
- action<ActionName extends string, I, O>(name: ActionName, action: Action<I, O, E>): Server<E, A & Record<ActionName, Action<I, O, E>>, InitialInput, InitialOutput>;
27
+ constructor(initial: Action<InitialInput, InitialOutput, E, P, MiddlewareOutput>, _middleware?: Action<MiddlewareInput, MiddlewareOutput, E, P, unknown> | null);
28
+ action<ActionName extends string, I, O>(name: ActionName, action: Action<I, O, E, P, MiddlewareOutput>): HandledServer<E, P, Prettify<A & Record<ActionName, Action<I, O, E, P, MiddlewareOutput>>>, InitialInput, InitialOutput, MiddlewareInput, MiddlewareOutput>;
12
29
  getData(): DataProvider<any, any, any>;
13
30
  }
14
31
  export interface Register {
15
32
  }
16
- export type AnyServer = Server<any, any>;
33
+ export type AnyServer = HandledServer<any, any>;
17
34
  export type RegisteredServer = Register extends {
18
35
  server: infer TServer extends AnyServer;
19
36
  } ? TServer : AnyServer;
20
- type GetInitialData<TServer extends AnyServer> = TServer extends Server<any, any, any, infer O> ? Exclude<O, undefined | null> : any;
37
+ export type GetMiddlewareInput<TServer extends AnyServer> = TServer extends HandledServer<any, any, any, any, any, infer I> ? I : any;
38
+ type GetInitialData<TServer extends AnyServer> = TServer extends HandledServer<any, any, any, any, infer O> ? Exclude<O, undefined | null> : any;
21
39
  export type InitialData = GetInitialData<RegisteredServer>;
22
40
  export type ComponentSettings = {
23
41
  id: string;
package/dist/Server.js CHANGED
@@ -2,10 +2,30 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getSettings = exports.getInitialData = exports.Server = void 0;
4
4
  class Server {
5
+ _middleware = null;
6
+ constructor() { }
7
+ middleware(action) {
8
+ this._middleware = action;
9
+ return this;
10
+ }
11
+ defineEnv() {
12
+ return this;
13
+ }
14
+ definePlugins() {
15
+ return this;
16
+ }
17
+ handler(action) {
18
+ return new HandledServer(action, this._middleware);
19
+ }
20
+ }
21
+ exports.Server = Server;
22
+ class HandledServer {
5
23
  initial;
24
+ _middleware;
6
25
  actions = {};
7
- constructor(initial) {
26
+ constructor(initial, _middleware = null) {
8
27
  this.initial = initial;
28
+ this._middleware = _middleware;
9
29
  }
10
30
  action(name, action) {
11
31
  this.actions[name] = action;
@@ -15,6 +35,10 @@ class Server {
15
35
  return async ({ action: actionName, params, setEmptyResponse, ...context }, cb) => {
16
36
  let res;
17
37
  try {
38
+ if (this._middleware) {
39
+ const data = await this._middleware(params, context);
40
+ context.state = data;
41
+ }
18
42
  if (actionName && this.actions[actionName]) {
19
43
  const data = params?.data ?? params;
20
44
  res = await this.actions[actionName](data, context);
@@ -31,7 +55,6 @@ class Server {
31
55
  };
32
56
  }
33
57
  }
34
- exports.Server = Server;
35
58
  const getInitialData = () => typeof __$$oc_initialData__ !== 'undefined'
36
59
  ? __$$oc_initialData__
37
60
  : {};
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ process.env.VITE_CJS_IGNORE_WARNING = 'true';
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_vm_1 = __importDefault(require("node:vm"));
11
+ const express_1 = __importDefault(require("express"));
12
+ const vite_1 = require("vite");
13
+ const esbuild_1 = __importDefault(require("esbuild"));
14
+ const oc_client_browser_1 = __importDefault(require("oc-client-browser"));
15
+ const cssModulesPlugin_1 = __importDefault(require("oc-vite-compiler/dist/lib/cssModulesPlugin"));
16
+ const get_mocked_plugins_js_1 = __importDefault(require("oc/dist/cli/domain/get-mocked-plugins.js"));
17
+ const node_util_1 = require("node:util");
18
+ const promisify = (fn) => (...args) => new Promise((resolve, reject) => fn(...args, (err, data) => (err ? reject(err) : resolve(data))));
19
+ const plugins = (0, get_mocked_plugins_js_1.default)({ err: () => { }, log: () => { }, ok: () => { }, warn: () => { } }, process.cwd());
20
+ const pkg = require(node_path_1.default.join(process.cwd(), 'package.json'));
21
+ const serverEntry = pkg.oc?.files?.data;
22
+ let appEntry = pkg.oc?.files?.template?.src || '';
23
+ if (!appEntry) {
24
+ console.log('Missing an entry on your package.json under oc.files.template.src');
25
+ process.exit(1);
26
+ }
27
+ if (!appEntry.startsWith('/')) {
28
+ appEntry = `/${appEntry}`;
29
+ }
30
+ const defaultParams = (() => {
31
+ let params = {};
32
+ if (pkg.oc?.parameters) {
33
+ params = Object.fromEntries(Object.entries(pkg.oc.parameters || {})
34
+ .filter(([, param]) => {
35
+ return !!param.mandatory && 'example' in param;
36
+ })
37
+ .map(([paramName, param]) => [paramName, param.example]));
38
+ }
39
+ return params;
40
+ })();
41
+ function getDataProvider() {
42
+ if (!serverEntry) {
43
+ return async (context) => ({ ...(context.params ?? {}) });
44
+ }
45
+ const result = esbuild_1.default.buildSync({
46
+ entryPoints: [serverEntry],
47
+ bundle: true,
48
+ format: 'cjs',
49
+ write: false,
50
+ });
51
+ const code = result.outputFiles[0].text;
52
+ const backend = node_vm_1.default.runInNewContext(code, {
53
+ ...globalThis,
54
+ exports: {},
55
+ module: { exports: {} },
56
+ });
57
+ const dataProvider = backend.data || backend.server.getData();
58
+ return promisify(dataProvider);
59
+ }
60
+ function getContext(req, params, action) {
61
+ const responseHeaders = {};
62
+ const context = {
63
+ action,
64
+ acceptLanguage: req.headers['accept-language'],
65
+ easeUrl: '/',
66
+ env: { name: 'local' },
67
+ params,
68
+ plugins,
69
+ renderComponent: () => {
70
+ throw new Error('renderComponent is not implemented in the server context');
71
+ },
72
+ renderComponents: () => {
73
+ throw new Error('renderComponents is not implemented in the server context');
74
+ },
75
+ requestHeaders: req.headers,
76
+ requestIp: req.ip,
77
+ setEmptyResponse: null,
78
+ staticPath: '/',
79
+ setHeader: (header, value) => {
80
+ if (!(typeof header === 'string' && typeof value === 'string')) {
81
+ throw new Error('context.setHeader parameters must be strings');
82
+ }
83
+ if (header && value) {
84
+ responseHeaders[header.toLowerCase()] = value;
85
+ }
86
+ },
87
+ templates: 'repository.getTemplatesInfo()',
88
+ };
89
+ return { context, responseHeaders };
90
+ }
91
+ function getBaseTemplate(appBlock) {
92
+ const baseTemplate = `
93
+ <!DOCTYPE html>
94
+ <html>
95
+ <head>
96
+ <meta name="robots" content="index, follow" />
97
+ <meta charset="utf-8" />
98
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
99
+ <style>
100
+ html, body {
101
+ width: 100%;
102
+ height: 100%;
103
+ margin: 0;
104
+ };
105
+ </style>
106
+ </head>
107
+ <body>
108
+ <script src="/oc-client/client.js"></script>
109
+ ${appBlock({ name: pkg.name, version: pkg.version, entry: appEntry })}
110
+ </body>
111
+ </html>
112
+ `;
113
+ return baseTemplate;
114
+ }
115
+ async function getHtmlTemplate(appBlock, filePath = 'index.html') {
116
+ let template;
117
+ try {
118
+ template = node_fs_1.default.readFileSync(node_path_1.default.resolve(process.cwd(), filePath), 'utf-8');
119
+ if (!template.includes('</body>')) {
120
+ throw new Error('If you use a custom html entry it has to have a closing body tag');
121
+ }
122
+ template = template.replace('</body>', `${appBlock}</body>`);
123
+ }
124
+ catch (error) {
125
+ template = getBaseTemplate(appBlock);
126
+ }
127
+ return template;
128
+ }
129
+ async function createServer({ template, port, }) {
130
+ const { dev: clientJs } = oc_client_browser_1.default.compileSync();
131
+ const app = (0, express_1.default)();
132
+ const dataProvider = getDataProvider();
133
+ app.use(express_1.default.json());
134
+ const vite = await (0, vite_1.createServer)({
135
+ server: { middlewareMode: true },
136
+ // @ts-ignore
137
+ plugins: [(0, cssModulesPlugin_1.default)(), template.plugin()],
138
+ mode: 'development',
139
+ optimizeDeps: {
140
+ exclude: ['fsevents'],
141
+ },
142
+ appType: 'custom',
143
+ });
144
+ app.use(vite.middlewares);
145
+ app.get('/oc-client/client.js', (req, res) => {
146
+ res.status(200).set({ 'Content-Type': 'text/javascript' }).send(clientJs);
147
+ });
148
+ app.post('/', async (req, res, next) => {
149
+ try {
150
+ const { action, parameters } = req.body.components[0];
151
+ const { context, responseHeaders } = getContext(req, parameters, action);
152
+ const props = await dataProvider(context);
153
+ const response = [
154
+ {
155
+ status: 200,
156
+ response: {
157
+ data: {
158
+ component: {
159
+ props,
160
+ },
161
+ },
162
+ },
163
+ },
164
+ ];
165
+ if (Object.keys(responseHeaders).length) {
166
+ res.set(responseHeaders);
167
+ }
168
+ res
169
+ .status(200)
170
+ .set({ 'Content-Type': 'application/json' })
171
+ .send(response);
172
+ }
173
+ catch (e) {
174
+ vite.ssrFixStacktrace(e);
175
+ next(e);
176
+ }
177
+ });
178
+ app.use('*', async (req, res, next) => {
179
+ const url = req.originalUrl;
180
+ try {
181
+ const params = defaultParams;
182
+ const { context, responseHeaders } = getContext(req, params);
183
+ let htmlTemplate = await getHtmlTemplate(template.appBlock);
184
+ const data = await dataProvider(context);
185
+ htmlTemplate = htmlTemplate.replace('__INITIAL_DATA__', JSON.stringify(data));
186
+ htmlTemplate = await vite.transformIndexHtml(url, htmlTemplate);
187
+ if (Object.keys(responseHeaders).length) {
188
+ res.set(responseHeaders);
189
+ }
190
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(htmlTemplate);
191
+ }
192
+ catch (e) {
193
+ vite.ssrFixStacktrace(e);
194
+ next(e);
195
+ }
196
+ });
197
+ app.listen(port);
198
+ console.log(`Listening on: http://localhost:${port}`);
199
+ }
200
+ const major = Number(process.versions.node.split('.')[0]) || 0;
201
+ if (major < 22) {
202
+ console.error('This script requires Node.js v22 or higher');
203
+ process.exit(1);
204
+ }
205
+ function cli(template) {
206
+ const { positionals: [command], values: { help, port = '5000' }, } = (0, node_util_1.parseArgs)({
207
+ allowPositionals: true,
208
+ options: {
209
+ port: {
210
+ type: 'string',
211
+ default: '5000',
212
+ short: 'p',
213
+ },
214
+ help: {
215
+ type: 'boolean',
216
+ default: false,
217
+ short: 'h',
218
+ },
219
+ },
220
+ });
221
+ if (help || !command) {
222
+ console.log(`
223
+ Usage:
224
+ $ oc-server <command> [options]
225
+
226
+ Commands:
227
+ dev Start the development server
228
+
229
+ Options:
230
+ -h, --help Display this message
231
+ `);
232
+ if (help && command === 'dev') {
233
+ console.log(`
234
+ Usage:
235
+ $ oc-server <command> [options]
236
+
237
+ Commands:
238
+ dev Start the development server
239
+
240
+ Options:
241
+ -h, --help Display this message
242
+ -p, --port Port to use (default: 5000)
243
+ `);
244
+ }
245
+ process.exit(0);
246
+ }
247
+ if (command === 'dev') {
248
+ createServer({ template, port });
249
+ }
250
+ }
251
+ let template;
252
+ try {
253
+ template = require(`${pkg.oc?.files?.template?.type}-compiler/dist/lib/hmr`);
254
+ }
255
+ catch (e) {
256
+ console.error(`The template ${pkg.oc?.files?.template?.type} is not supported. Try updating to the last version`);
257
+ process.exit(1);
258
+ }
259
+ cli(template);
@@ -1,8 +1,13 @@
1
- import { AnyServer, RegisteredServer, Action } from './Server';
2
- type InferInput<R> = R extends Action<infer I, any, any> ? I : any;
3
- type InferOutput<R> = R extends Action<any, infer O, any> ? O : never;
1
+ import { AnyServer, RegisteredServer, Action, GetMiddlewareInput } from './Server';
2
+ type Prettify<T> = {
3
+ [K in keyof T]: T[K];
4
+ } & {};
5
+ type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
6
+ type IsAny<T> = IfAny<T, true, never>;
7
+ type InferInput<R> = R extends Action<infer I, any, any, any, any> ? I : any;
8
+ type InferOutput<R> = R extends Action<any, infer O, any, any, any> ? O : never;
4
9
  type ServerClient<TServer extends AnyServer> = {
5
- readonly [Property in keyof TServer['actions']]: (input: InferInput<TServer['actions'][Property]>) => Promise<InferOutput<TServer['actions'][Property]>>;
10
+ readonly [Property in keyof TServer['actions']]: (input: Prettify<InferInput<TServer['actions'][Property]> & (IsAny<GetMiddlewareInput<TServer>> extends never ? GetMiddlewareInput<TServer> : {})>) => Promise<InferOutput<TServer['actions'][Property]>>;
6
11
  };
7
12
  export declare const serverClient: ServerClient<RegisteredServer>;
8
13
  export type ActionOutput<K extends keyof typeof serverClient> = Awaited<ReturnType<(typeof serverClient)[K]>>;
package/dist/types.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface Template {
17
17
  version: string;
18
18
  externals: External[];
19
19
  }
20
- export interface DataContext<T = any, E = Env, P = any> {
20
+ export type DataContext<T = any, E = Env, P = any, S = unknown> = {
21
21
  action?: string;
22
22
  acceptLanguage: AcceptLanguage[];
23
23
  baseUrl: string;
@@ -30,7 +30,9 @@ export interface DataContext<T = any, E = Env, P = any> {
30
30
  setHeader: (header: string, value: string) => void;
31
31
  staticPath: string;
32
32
  templates: Template[];
33
- }
33
+ } & (S extends Record<string, unknown> ? {
34
+ state: S;
35
+ } : {});
34
36
  type Callback<D, E = Error> = (error: E | null, data?: D) => void;
35
37
  export type DataProvider<Parameters = any, Return = any, Environment = Env> = (context: DataContext<Parameters, Environment>, callback: Callback<Return>) => void;
36
38
  export {};
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "oc-server",
3
- "version": "0.4.5",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "bin": "dist/cli.js",
7
8
  "scripts": {
8
9
  "build": "tsc",
9
10
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
@@ -19,5 +20,11 @@
19
20
  "files": [
20
21
  "dist/**"
21
22
  ],
22
- "license": "MIT"
23
- }
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "oc": "^0.49.57"
26
+ },
27
+ "devDependencies": {
28
+ "@types/express": "^4.17.21"
29
+ }
30
+ }