@appsemble/lang-sdk 0.40.7-node-brotli.0 → 0.40.8

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/BasicValidator.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Validator, } from 'jsonschema';
2
2
  import { isValidIconReference } from './icons.js';
3
3
  import { schemas as allSchemas } from './index.js';
4
+ import { skipBaseResolutionForSchemasWithoutId } from './jsonschemaBaseResolution.js';
4
5
  import { escapeJsonPointer } from './jsonPointer.js';
5
6
  import { has } from './miscellaneous.js';
6
7
  export class BaseValidatorFactory {
@@ -9,6 +10,7 @@ export class BaseValidatorFactory {
9
10
  this.schemas = schemas;
10
11
  }
11
12
  build() {
13
+ skipBaseResolutionForSchemasWithoutId();
12
14
  const validator = new Validator();
13
15
  for (const [key, format] of Object.entries({
14
16
  ...BaseValidatorFactory.defaultCustomFormats,
package/blockUtils.js CHANGED
@@ -49,6 +49,9 @@ export function getAppBlocks(definition) {
49
49
  const result = [];
50
50
  iterApp(definition, {
51
51
  onBlock({ type, version }) {
52
+ if (typeof type !== 'string' || typeof version !== 'string') {
53
+ return;
54
+ }
52
55
  const name = normalizeBlockName(type);
53
56
  const asString = `${name}@${version}`;
54
57
  if (!visited.has(asString)) {
package/iterApp.js CHANGED
@@ -25,6 +25,9 @@ export function iterAction(action, callbacks, prefix = []) {
25
25
  if (action.type === 'each') {
26
26
  return iterAction(action.do, callbacks, [...prefix, 'do']);
27
27
  }
28
+ if (action.type === 'match') {
29
+ return action.match.some((matchCase, index) => iterAction(matchCase.action, callbacks, [...prefix, 'match', index, 'action']));
30
+ }
28
31
  if ('blocks' in action) {
29
32
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
30
33
  return iterBlockList(action.blocks, callbacks, [...prefix, 'blocks']);
@@ -137,7 +140,10 @@ export function iterPage(page, callbacks, prefix = []) {
137
140
  }
138
141
  return result;
139
142
  }
140
- const { blocks = [] } = page;
143
+ const { actions, blocks = [] } = page;
144
+ if (Object.entries(actions ?? {}).some(([key, action]) => iterAction(action, callbacks, [...prefix, 'actions', key]))) {
145
+ return true;
146
+ }
141
147
  return iterBlockList(blocks, callbacks, [...prefix, 'blocks']);
142
148
  }
143
149
  /**
@@ -162,6 +168,13 @@ export function iterApp(app, callbacks) {
162
168
  }
163
169
  }
164
170
  }
171
+ if (app.webhooks) {
172
+ for (const [name, webhook] of Object.entries(app.webhooks)) {
173
+ if (webhook?.action && iterAction(webhook.action, callbacks, ['webhooks', name, 'action'])) {
174
+ return true;
175
+ }
176
+ }
177
+ }
165
178
  return false;
166
179
  }
167
180
  //# sourceMappingURL=iterApp.js.map
package/jsonschema.d.ts CHANGED
@@ -23,6 +23,7 @@ declare module 'jsonschema' {
23
23
  }
24
24
  interface SchemaContext {
25
25
  path: (number | string)[];
26
+ resolve: (target: string) => string;
26
27
  }
27
28
  }
28
29
  /**
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Resolve a child schema's base URI only when that schema declares an id.
3
+ *
4
+ * `jsonschema` resolves a base URI for every schema node it visits, and that URL resolution is the
5
+ * single most expensive part of validating a large app definition. A schema without an `$id` or
6
+ * `id` always resolves to its parent's base, so the resolution only carries information when an id
7
+ * is present. Appsemble's schemas declare no ids, which makes the resolution pure overhead for
8
+ * every node in the document.
9
+ *
10
+ * Calling this more than once is harmless: it installs the same implementation each time rather
11
+ * than wrapping whatever is already there.
12
+ */
13
+ export declare function skipBaseResolutionForSchemasWithoutId(): void;
@@ -0,0 +1,30 @@
1
+ // `jsonschema` ships no types for its internal helpers module, which is where the schema context
2
+ // it builds while validating lives.
3
+ // @ts-expect-error 7016 -- untyped module
4
+ import { SchemaContext as UntypedSchemaContext } from 'jsonschema/lib/helpers.js';
5
+ const SchemaContextConstructor = UntypedSchemaContext;
6
+ /**
7
+ * Resolve a child schema's base URI only when that schema declares an id.
8
+ *
9
+ * `jsonschema` resolves a base URI for every schema node it visits, and that URL resolution is the
10
+ * single most expensive part of validating a large app definition. A schema without an `$id` or
11
+ * `id` always resolves to its parent's base, so the resolution only carries information when an id
12
+ * is present. Appsemble's schemas declare no ids, which makes the resolution pure overhead for
13
+ * every node in the document.
14
+ *
15
+ * Calling this more than once is harmless: it installs the same implementation each time rather
16
+ * than wrapping whatever is already there.
17
+ */
18
+ export function skipBaseResolutionForSchemasWithoutId() {
19
+ SchemaContextConstructor.prototype.makeChild = function makeChild(schema, propertyName) {
20
+ const path = propertyName === undefined ? this.path : [...this.path, propertyName];
21
+ const id = schema.$id ?? schema.id;
22
+ const base = id ? this.resolve(id) : this.base;
23
+ const ctx = new SchemaContextConstructor(schema, this.options, path, base, Object.create(this.schemas));
24
+ if (id && !ctx.schemas[base]) {
25
+ ctx.schemas[base] = schema;
26
+ }
27
+ return ctx;
28
+ };
29
+ }
30
+ //# sourceMappingURL=jsonschemaBaseResolution.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsemble/lang-sdk",
3
- "version": "0.40.7-node-brotli.0",
3
+ "version": "0.40.8",
4
4
  "description": "Language SDK for Appsemble",
5
5
  "keywords": [
6
6
  "app",
package/validation.js CHANGED
@@ -9,7 +9,7 @@ import { findPageByName } from './findPageByName.js';
9
9
  import { isAppLink } from './pageUtils.js';
10
10
  import { bottomNavigationGridArea, breadcrumbsGridArea, builtinPagesGridAreaOrder, getBuiltinPagesDefaultGridLayout, getCascadedGridTemplateAreas, getGridTemplateAreas, gridDeviceOrder, resendBannerGridArea, } from './gridUtils.js';
11
11
  import { isValidIconName, parseIconReference } from './icons.js';
12
- import { iterAction, iterApp } from './iterApp.js';
12
+ import { iterApp } from './iterApp.js';
13
13
  import { has } from './miscellaneous.js';
14
14
  import { normalize } from './normalize.js';
15
15
  import { serverActions } from './serverActions.js';
@@ -1467,6 +1467,10 @@ function validateActions(definition, report) {
1467
1467
  report(action.type, 'action type is not supported for cron jobs', [...path, 'type']);
1468
1468
  return;
1469
1469
  }
1470
+ if (path[0] === 'webhooks' && !serverActions.has(action.type)) {
1471
+ report(action.type, 'action type is not supported for webhooks', [...path, 'type']);
1472
+ return;
1473
+ }
1470
1474
  if (validateUnsupportedServerOptimisticResourceWrite(action, path)) {
1471
1475
  return;
1472
1476
  }
@@ -1502,7 +1506,8 @@ function validateActions(definition, report) {
1502
1506
  report(action.type, 'refers to a resource that doesn’t exist', [...path, 'resource']);
1503
1507
  return;
1504
1508
  }
1505
- if (!action.type.startsWith('resource.subscription.')) {
1509
+ // Webhook actions run on the server without checking resource permissions.
1510
+ if (!action.type.startsWith('resource.subscription.') && path[0] !== 'webhooks') {
1506
1511
  if (!definition.security) {
1507
1512
  report(action.type, 'missing security definition', [...path, 'resource']);
1508
1513
  return;
@@ -1637,16 +1642,6 @@ function validateActions(definition, report) {
1637
1642
  }
1638
1643
  },
1639
1644
  });
1640
- for (const [name, webhook] of Object.entries(definition.webhooks ?? {})) {
1641
- if (webhook.action &&
1642
- iterAction(webhook.action, {
1643
- onAction(action, path) {
1644
- return validateUnsupportedServerOptimisticResourceWrite(action, path);
1645
- },
1646
- }, ['webhooks', name, 'action'])) {
1647
- return;
1648
- }
1649
- }
1650
1645
  }
1651
1646
  function validateEvents(definition, blockVersions, report) {
1652
1647
  const indexMap = new Map();