@webtypen/webframez-core 0.3.96 → 0.3.100

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
@@ -996,7 +996,10 @@ Metro consumers can call
996
996
  `require('@webtypen/webframez-core/build-cache').configureMetroCache(config)`.
997
997
  It applies `WEBFRAMEZ_BUILD_MAX_WORKERS` and `WEBFRAMEZ_METRO_CACHE_DIRECTORY`,
998
998
  preserving Expo's FileStore implementation and supporting old/new file-map option
999
- names. Do not pass Expo `--clear` on normal cached builds.
999
+ names. Transform and file-map caches are separated by a stable hash of sorted
1000
+ `EXPO_PUBLIC_*`, `NODE_ENV` and `BABEL_ENV` values. The cache version also includes
1001
+ this hash for custom/remote stores; public values are not written into path names.
1002
+ Private registry credentials do not participate in this build identity. Do not pass Expo `--clear` on normal cached builds.
1000
1003
 
1001
1004
 
1002
1005
  ### Reusable build workspaces
@@ -1024,3 +1027,9 @@ Asset builds use a stable staging directory so bundler caches can reuse module p
1024
1027
  An exclusive per-output lock prevents concurrent builds overwriting that directory.
1025
1028
  After a killed local build, remove its `.webframez-build/.building-*.lock` directory
1026
1029
  only after confirming that no previous build is running. WSM Force Clear removes it.
1030
+
1031
+ ### Explicit HEAD routes
1032
+
1033
+ `Route.head(path, handler, options)` registers metadata-only requests separately from GET.
1034
+ Group prefixes, middleware, domain filters and OPTIONS preflight resolution work as for other routes.
1035
+ Return headers with `res.header(...)` and finish with `res.end()`; no response body is needed.
package/build-cache.d.ts CHANGED
@@ -1 +1 @@
1
- export function configureMetroCache<T extends {cacheStores?: any; maxWorkers?: number}>(config:T, env?:Record<string,string | undefined>):T;
1
+ export function configureMetroCache<T extends {cacheVersion?: string; cacheStores?: any; maxWorkers?: number}>(config:T, env?:Record<string,string | undefined>):T;
package/build-cache.js CHANGED
@@ -1,15 +1,26 @@
1
1
  const path = require("node:path");
2
2
  const fs = require("node:fs");
3
+ const crypto = require("node:crypto");
3
4
 
4
5
  /** Preserve the platform's FileStore subclass (including Expo CSS handling). */
5
6
  function configureMetroCache(config, env = process.env) {
7
+ // Expo inlines public values into transforms. Metro's persistent cache must
8
+ // not share those transforms between origins or feature configurations.
9
+ const publicEnvironment = Object.keys(env)
10
+ .filter(key => key.startsWith("EXPO_PUBLIC_") || key === "NODE_ENV" || key === "BABEL_ENV")
11
+ .sort()
12
+ .map(key => [key, env[key]]);
13
+ const environmentKey = crypto.createHash("sha256").update(JSON.stringify(publicEnvironment)).digest("hex");
14
+ config.cacheVersion = `${config.cacheVersion || "1.0"}:webframez-public-env-v1:${environmentKey}`;
15
+
16
+
6
17
  if (env.WEBFRAMEZ_BUILD_MAX_WORKERS !== undefined) {
7
18
  const workers = Number(env.WEBFRAMEZ_BUILD_MAX_WORKERS);
8
19
  if (!Number.isInteger(workers) || workers < 1 || workers > 64) throw new Error("Invalid WEBFRAMEZ_BUILD_MAX_WORKERS");
9
20
  config.maxWorkers = workers;
10
21
  }
11
22
  if (!env.WEBFRAMEZ_METRO_CACHE_DIRECTORY) return config;
12
- const root = path.resolve(env.WEBFRAMEZ_METRO_CACHE_DIRECTORY);
23
+ const root = path.join(path.resolve(env.WEBFRAMEZ_METRO_CACHE_DIRECTORY), `env-${environmentKey}`);
13
24
  const relocate = stores => stores.map((store, index) => {
14
25
  // Leave remote and custom stores untouched; their constructors need other options.
15
26
  if (!store || typeof store._root !== "string") return store;
@@ -18,7 +18,7 @@ const node_path_1 = __importDefault(require("node:path"));
18
18
  const node_crypto_1 = __importDefault(require("node:crypto"));
19
19
  /** A separate process releases the compiler AST before runtime copies and bundling. */
20
20
  function compile({ projectRoot, tsconfig, outDir, buildRoot, root, stagingDir }) {
21
- var _a;
21
+ var _a, _b;
22
22
  return __awaiter(this, void 0, void 0, function* () {
23
23
  const ts = require(require.resolve("typescript", { paths: [projectRoot, __dirname] }));
24
24
  const scope = node_crypto_1.default.createHash("sha256").update(tsconfig).digest("hex").slice(0, 24);
@@ -33,21 +33,32 @@ function compile({ projectRoot, tsconfig, outDir, buildRoot, root, stagingDir })
33
33
  // Compact runners may place the source checkout inside the shared cache root.
34
34
  const generated = [outDir, buildRoot, inside(root, projectRoot) ? cache : root, parsed.options.outDir].filter(Boolean);
35
35
  const files = parsed.fileNames.filter((file) => !generated.some(directory => inside(directory, file)));
36
- const key = node_crypto_1.default.createHash("sha256").update(JSON.stringify([files, parsed.options, ts.version])).digest("hex");
36
+ const key = node_crypto_1.default.createHash("sha256").update(JSON.stringify(["bounded-generated-imports-v1", files, parsed.options, ts.version])).digest("hex");
37
37
  const previous = yield promises_1.default.readFile(node_path_1.default.join(cache, "key"), "utf8").catch(() => "");
38
38
  if (previous !== key)
39
39
  yield promises_1.default.rm(cache, { recursive: true, force: true });
40
40
  yield promises_1.default.mkdir(output, { recursive: true });
41
41
  console.log(`[webframez] TypeScript cache: ${previous === key ? "reuse" : "cold"} (${files.length} source files)`);
42
42
  try {
43
- const builder = ts.createIncrementalProgram({ rootNames: files, options: Object.assign(Object.assign({}, parsed.options), { outDir: output, incremental: true, tsBuildInfoFile: node_path_1.default.join(cache, "build.tsbuildinfo") }), projectReferences: parsed.projectReferences });
43
+ const options = Object.assign(Object.assign({}, parsed.options), { outDir: output, incremental: true, tsBuildInfoFile: node_path_1.default.join(cache, "build.tsbuildinfo") });
44
+ const host = ts.createIncrementalCompilerHost(options);
45
+ // JavaScript scripts may require the previous build for smoke checks.
46
+ // Do not let those imports pull generated code and a second dependency
47
+ // tree back into compilation. The last good output remains untouched.
48
+ const hidden = (file) => file !== options.tsBuildInfoFile && generated.some(directory => inside(directory, node_path_1.default.resolve(file)));
49
+ for (const name of ["fileExists", "directoryExists", "readFile"]) {
50
+ const original = (_a = host[name]) === null || _a === void 0 ? void 0 : _a.bind(host);
51
+ if (original)
52
+ host[name] = (file) => hidden(file) ? (name === "readFile" ? undefined : false) : original(file);
53
+ }
54
+ const builder = ts.createIncrementalProgram({ rootNames: files, options, host, projectReferences: parsed.projectReferences });
44
55
  const diagnostics = [...(loaded.error ? [loaded.error] : []), ...parsed.errors, ...builder.getOptionsDiagnostics(), ...builder.getGlobalDiagnostics(), ...builder.getSyntacticDiagnostics(), ...builder.getSemanticDiagnostics()];
45
56
  if (diagnostics.length)
46
57
  console.error(ts.formatDiagnosticsWithColorAndContext(diagnostics, { getCanonicalFileName: (name) => name, getCurrentDirectory: () => projectRoot, getNewLine: () => "\n" }));
47
58
  if (diagnostics.some((entry) => entry.category === ts.DiagnosticCategory.Error))
48
59
  throw new Error("tsc exited with code 1");
49
60
  const emitted = builder.emit();
50
- if ((_a = emitted.diagnostics) === null || _a === void 0 ? void 0 : _a.length) {
61
+ if ((_b = emitted.diagnostics) === null || _b === void 0 ? void 0 : _b.length) {
51
62
  console.error(ts.formatDiagnosticsWithColorAndContext(emitted.diagnostics, { getCanonicalFileName: (name) => name, getCurrentDirectory: () => projectRoot, getNewLine: () => "\n" }));
52
63
  if (emitted.diagnostics.some((entry) => entry.category === ts.DiagnosticCategory.Error))
53
64
  throw new Error("tsc exited with code 1");
@@ -27,6 +27,8 @@ export declare class RouteFacade {
27
27
  * @param options
28
28
  */
29
29
  get(path: string, component: any, options?: RouteRegistrationOptions): void;
30
+ /** Register a HEAD route, for metadata without a response body. */
31
+ head(path: string, component: any, options?: RouteRegistrationOptions): void;
30
32
  /** Register the existing DataBuilder POST protocol with request-local configuration. */
31
33
  databuilder(path: string, options?: DataBuilderRouteOptions): void;
32
34
  /** Register CSRF, model login, logout and refresh using the browser auth contract. */
@@ -77,6 +77,10 @@ class RouteFacade {
77
77
  get(path, component, options) {
78
78
  this.registerWithGroupContext("GET", path, component, options);
79
79
  }
80
+ /** Register a HEAD route, for metadata without a response body. */
81
+ head(path, component, options) {
82
+ this.registerWithGroupContext("HEAD", path, component, options);
83
+ }
80
84
  /** Register the existing DataBuilder POST protocol with request-local configuration. */
81
85
  databuilder(path, options = {}) {
82
86
  this.registerWithGroupContext("POST", path, (0, DataBuilderRoute_1.dataBuilderRoute)(options), { middleware: options.middleware, domains: options.domains });
@@ -43,6 +43,10 @@ declare class RouterFacade {
43
43
  routesGET: {
44
44
  [key: string]: RouteObject[];
45
45
  };
46
+ /** Explicit HEAD routes, with the same middleware and domain matching as GET. */
47
+ routesHEAD: {
48
+ [key: string]: RouteObject[];
49
+ };
46
50
  /**
47
51
  * POST Route Store
48
52
  */
@@ -53,6 +53,8 @@ class RouterFacade {
53
53
  * GET Route Store
54
54
  */
55
55
  this.routesGET = {};
56
+ /** Explicit HEAD routes, with the same middleware and domain matching as GET. */
57
+ this.routesHEAD = {};
56
58
  /**
57
59
  * POST Route Store
58
60
  */
@@ -86,6 +88,7 @@ class RouterFacade {
86
88
  ? options.maxRequestBodySizeBytes
87
89
  : 10 * 1024 * 1024;
88
90
  this.routesGET = {};
91
+ this.routesHEAD = {};
89
92
  this.routesPOST = {};
90
93
  this.routesPUT = {};
91
94
  this.routesPATCH = {};
@@ -153,6 +156,12 @@ class RouterFacade {
153
156
  }
154
157
  this.routesGET[path].push(routeConf);
155
158
  }
159
+ else if (type === "HEAD") {
160
+ if (!this.routesHEAD[path]) {
161
+ this.routesHEAD[path] = [];
162
+ }
163
+ this.routesHEAD[path].push(routeConf);
164
+ }
156
165
  else if (type === "POST") {
157
166
  if (!this.routesPOST[path]) {
158
167
  this.routesPOST[path] = [];
@@ -196,6 +205,9 @@ class RouterFacade {
196
205
  if (req.method === "GET") {
197
206
  return this.dissolveRoute(this.routesGET, cleanUrl, req);
198
207
  }
208
+ else if (req.method === "HEAD") {
209
+ return this.dissolveRoute(this.routesHEAD, cleanUrl, req);
210
+ }
199
211
  else if (req.method === "POST") {
200
212
  return this.dissolveRoute(this.routesPOST, cleanUrl, req);
201
213
  }
@@ -212,6 +224,9 @@ class RouterFacade {
212
224
  if (req.headers["access-control-request-method"] === "GET") {
213
225
  return this.dissolveRoute(this.routesGET, cleanUrl, req);
214
226
  }
227
+ else if (req.headers["access-control-request-method"] === "HEAD") {
228
+ return this.dissolveRoute(this.routesHEAD, cleanUrl, req);
229
+ }
215
230
  else if (req.headers["access-control-request-method"] === "POST") {
216
231
  return this.dissolveRoute(this.routesPOST, cleanUrl, req);
217
232
  }
package/dist/index.d.ts CHANGED
@@ -76,3 +76,4 @@ export { ModelForm, ModelFormContext, ModelFormConstructor } from "./DataBuilder
76
76
  export { Field, Validates, Forms, DataBuilderFieldOptions } from "./DataBuilder/decorators";
77
77
  export { DataBuilderRouteOptions } from "./DataBuilder/DataBuilderRoute";
78
78
  export { LegacyFieldTypeAdapter } from "./DataBuilder/LegacyFieldTypeAdapter";
79
+ export { defaultFormFields } from "./DataBuilder/formScope";
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.LegacyFieldTypeAdapter = exports.Forms = exports.Validates = exports.Field = exports.ModelForm = void 0;
17
+ exports.defaultFormFields = exports.LegacyFieldTypeAdapter = exports.Forms = exports.Validates = exports.Field = exports.ModelForm = void 0;
18
18
  __exportStar(require("./Config"), exports);
19
19
  __exportStar(require("./Functions/DateFunctions"), exports);
20
20
  __exportStar(require("./Functions/FileFunctions"), exports);
@@ -96,3 +96,5 @@ Object.defineProperty(exports, "Validates", { enumerable: true, get: function ()
96
96
  Object.defineProperty(exports, "Forms", { enumerable: true, get: function () { return decorators_1.Forms; } });
97
97
  var LegacyFieldTypeAdapter_1 = require("./DataBuilder/LegacyFieldTypeAdapter");
98
98
  Object.defineProperty(exports, "LegacyFieldTypeAdapter", { enumerable: true, get: function () { return LegacyFieldTypeAdapter_1.LegacyFieldTypeAdapter; } });
99
+ var formScope_1 = require("./DataBuilder/formScope");
100
+ Object.defineProperty(exports, "defaultFormFields", { enumerable: true, get: function () { return formScope_1.defaultFormFields; } });
@@ -32,6 +32,11 @@ Metadata is inherited without mutating parent definitions. Decorated definitions
32
32
 
33
33
  Without registered forms or legacy `__forms`, decorated models get a default form containing their fields, including child layouts. Existing `__schema` still carries version, collection, hooks, aggregation, uniqueness, new-data and deletion behavior. Defining `static forms = () => [UserProfileForm]` is an alternative to `@Forms()`; the decorator takes precedence.
34
34
 
35
+ For dynamic sections inside an explicit layout, import `defaultFormFields` from
36
+ `@webtypen/webframez-core` and use `{ field: "attributes", fields: defaultFormFields(attributeSchema) }`.
37
+ It recursively supplies the `fields` layouts for object and array schemas, including
38
+ objects inside array entries. It does not modify the schema or populate data values.
39
+
35
40
  ## Server-side form classes
36
41
 
37
42
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-core",
3
- "version": "0.3.96",
3
+ "version": "0.3.100",
4
4
  "description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
5
5
  "homepage": "https://webtypen.de/",
6
6
  "license": "MIT",