@jskit-ai/crud-core 0.1.62 → 0.1.64

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.
@@ -1,12 +1,14 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/crud-core",
4
- version: "0.1.62",
4
+ version: "0.1.64",
5
5
  kind: "runtime",
6
6
  description: "Shared CRUD helpers used by CRUD modules.",
7
7
  dependsOn: [
8
+ "@jskit-ai/http-runtime",
8
9
  "@jskit-ai/kernel",
9
10
  "@jskit-ai/realtime",
11
+ "@jskit-ai/resource-crud-core",
10
12
  "@jskit-ai/shell-web",
11
13
  "@jskit-ai/users-core",
12
14
  "@jskit-ai/users-web"
@@ -26,7 +28,7 @@ export default Object.freeze({
26
28
  mutations: {
27
29
  dependencies: {
28
30
  runtime: {
29
- "@jskit-ai/crud-core": "0.1.62"
31
+ "@jskit-ai/crud-core": "0.1.64"
30
32
  },
31
33
  dev: {}
32
34
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/crud-core",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -10,8 +10,9 @@
10
10
  "./client/composables/createCrudClientSupport": "./src/client/composables/createCrudClientSupport.js",
11
11
  "./client/composables/crudClientSupportHelpers": "./src/client/composables/crudClientSupportHelpers.js",
12
12
  "./client/composables/useCrudRealtimeInvalidation": "./src/client/composables/useCrudRealtimeInvalidation.js",
13
- "./shared/crudFieldMetaSupport": "./src/shared/crudFieldMetaSupport.js",
13
+ "./shared/crudFieldSupport": "./src/shared/crudFieldSupport.js",
14
14
  "./shared/crudNamespaceSupport": "./src/shared/crudNamespaceSupport.js",
15
+ "./shared/crudResource": "./src/shared/crudResource.js",
15
16
  "./server/repositorySupport": "./src/server/repositorySupport.js",
16
17
  "./server/resourceRuntime": "./src/server/resourceRuntime/index.js",
17
18
  "./server/createHooksToCollectChildren": "./src/server/createHooksToCollectChildren.js",
@@ -22,16 +23,23 @@
22
23
  "./server/createCrudServiceFromResource": "./src/server/createCrudServiceFromResource.js",
23
24
  "./server/crudModuleConfig": "./src/server/crudModuleConfig.js",
24
25
  "./server/listQueryValidators": "./src/server/listQueryValidators.js",
25
- "./server/listFilters": "./src/server/listFilters.js"
26
+ "./server/listFilters": "./src/server/listFilters.js",
27
+ "./server/routeContracts": "./src/server/routeContracts.js"
26
28
  },
27
29
  "dependencies": {
28
30
  "@tanstack/vue-query": "^5.90.5",
29
- "@jskit-ai/database-runtime": "0.1.54",
30
- "@jskit-ai/kernel": "0.1.54",
31
- "@jskit-ai/realtime": "0.1.53",
32
- "@jskit-ai/shell-web": "0.1.53",
33
- "@jskit-ai/users-core": "0.1.64",
34
- "@jskit-ai/users-web": "0.1.69",
35
- "typebox": "^1.0.81"
31
+ "@jskit-ai/database-runtime": "0.1.56",
32
+ "@jskit-ai/http-runtime": "0.1.55",
33
+ "@jskit-ai/kernel": "0.1.56",
34
+ "json-rest-schema": "1.x.x",
35
+ "@jskit-ai/realtime": "0.1.55",
36
+ "@jskit-ai/resource-crud-core": "0.1.1",
37
+ "@jskit-ai/shell-web": "0.1.55",
38
+ "@jskit-ai/users-core": "0.1.66",
39
+ "@jskit-ai/users-web": "0.1.71"
40
+ },
41
+ "peerDependencies": {
42
+ "vue": "^3.5.13",
43
+ "vue-router": "^5.0.4"
36
44
  }
37
45
  }
@@ -20,6 +20,7 @@ const CRUD_REQUESTED_OWNERSHIP_FILTER_SET = new Set([
20
20
  CRUD_REQUESTED_OWNERSHIP_FILTER_AUTO
21
21
  ]);
22
22
  const CRUD_MODULE_ID = "crud";
23
+ const WORKSPACE_CAPABLE_TENANCY_MODES = new Set(["personal", "workspaces"]);
23
24
 
24
25
  function asRecord(value) {
25
26
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -227,11 +228,30 @@ function resolveCrudSurfacePolicy(
227
228
 
228
229
  function resolveCrudSurfacePolicyFromAppConfig(sourceConfig = {}, appConfig = {}, options = {}) {
229
230
  const config = asRecord(appConfig);
230
- return resolveCrudSurfacePolicy(sourceConfig, {
231
- ...asRecord(options),
232
- surfaceDefinitions: config.surfaceDefinitions,
233
- defaultSurfaceId: config.surfaceDefaultId
234
- });
231
+ const requestedSurfaceId = normalizeSurfaceId(asRecord(sourceConfig).surface);
232
+ const fallbackSurfaceId = normalizeSurfaceId(config.surfaceDefaultId);
233
+ const resolvedSurfaceId = requestedSurfaceId || fallbackSurfaceId;
234
+
235
+ try {
236
+ return resolveCrudSurfacePolicy(sourceConfig, {
237
+ ...asRecord(options),
238
+ surfaceDefinitions: config.surfaceDefinitions,
239
+ defaultSurfaceId: config.surfaceDefaultId
240
+ });
241
+ } catch (error) {
242
+ const normalizedTenancyMode = normalizeText(config.tenancyMode).toLowerCase();
243
+ const message = String(error?.message || "");
244
+ if (
245
+ message.includes("cannot resolve surface") &&
246
+ (resolvedSurfaceId === "admin" || resolvedSurfaceId === "app") &&
247
+ WORKSPACE_CAPABLE_TENANCY_MODES.has(normalizedTenancyMode)
248
+ ) {
249
+ error.message = `${message} Workspace-capable tenancy mode "${normalizedTenancyMode}" usually requires ` +
250
+ '@jskit-ai/workspaces-core, which defines the "app" and "admin" surfaces. ' +
251
+ "Install that package or add matching surface definitions in config/public.js.";
252
+ }
253
+ throw error;
254
+ }
235
255
  }
236
256
 
237
257
  function resolveCrudConfigsFromModules(modulesSource = {}) {
@@ -1,34 +1,8 @@
1
1
  import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
2
+ import { resolveCrudFieldSchemaProperties } from "@jskit-ai/kernel/shared/support/crudFieldContract";
2
3
  import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
4
  import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
4
5
 
5
- function isSchemaNullable(schema = {}) {
6
- if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
7
- return false;
8
- }
9
-
10
- const schemaType = schema.type;
11
- if (typeof schemaType === "string" && normalizeText(schemaType).toLowerCase() === "null") {
12
- return true;
13
- }
14
- if (Array.isArray(schemaType)) {
15
- const hasNullType = schemaType.some((entry) => normalizeText(entry).toLowerCase() === "null");
16
- if (hasNullType) {
17
- return true;
18
- }
19
- }
20
-
21
- const variants = [];
22
- if (Array.isArray(schema.anyOf)) {
23
- variants.push(...schema.anyOf);
24
- }
25
- if (Array.isArray(schema.oneOf)) {
26
- variants.push(...schema.oneOf);
27
- }
28
-
29
- return variants.some((entry) => isSchemaNullable(entry));
30
- }
31
-
32
6
  function normalizeFieldSet(value, { context = "crudFieldAccess", label = "field list" } = {}) {
33
7
  if (value == null || value === "*") {
34
8
  return null;
@@ -73,17 +47,13 @@ function resolveWriteMode(fieldAccess = {}, { context = "crudFieldAccess" } = {}
73
47
  }
74
48
 
75
49
  function buildOutputFieldRules(resource = {}) {
76
- const viewOutputSchema = resource?.operations?.view?.outputValidator?.schema;
77
- if (!viewOutputSchema || typeof viewOutputSchema !== "object" || Array.isArray(viewOutputSchema)) {
50
+ const outputProperties = resolveCrudFieldSchemaProperties(resource?.operations?.view?.output, {
51
+ context: "crudFieldAccess resource.operations.view.output"
52
+ });
53
+ if (Object.keys(outputProperties).length < 1) {
78
54
  return null;
79
55
  }
80
56
 
81
- const outputProperties = normalizeObject(viewOutputSchema.properties);
82
- const requiredFields = new Set(
83
- (Array.isArray(viewOutputSchema.required) ? viewOutputSchema.required : [])
84
- .map((entry) => normalizeText(entry))
85
- .filter(Boolean)
86
- );
87
57
  const fieldRules = new Map();
88
58
 
89
59
  for (const [fieldKey, fieldSchemaRaw] of Object.entries(outputProperties)) {
@@ -96,8 +66,8 @@ function buildOutputFieldRules(resource = {}) {
96
66
  fieldRules.set(
97
67
  normalizedFieldKey,
98
68
  Object.freeze({
99
- required: requiredFields.has(normalizedFieldKey),
100
- nullable: isSchemaNullable(fieldSchema),
69
+ required: fieldSchema.required === true,
70
+ nullable: fieldSchema.nullable === true,
101
71
  hasDefault: Object.hasOwn(fieldSchema, "default"),
102
72
  defaultValue: fieldSchema.default
103
73
  })
@@ -210,7 +180,7 @@ function applyReadableFieldPolicyToRecord(record, allowedFields, outputRules = n
210
180
  }
211
181
 
212
182
  throw new Error(
213
- `${context} cannot redact required non-nullable field "${fieldKey}" without schema.default.`
183
+ `${context} cannot redact required non-nullable field "${fieldKey}" without a default value.`
214
184
  );
215
185
  }
216
186
 
@@ -233,7 +203,7 @@ function createCrudFieldAccessRuntime(resource = {}, { context = "crudFieldAcces
233
203
  return null;
234
204
  }
235
205
  if (!outputRules) {
236
- throw new TypeError(`${context} requires resource.operations.view.outputValidator.schema for fieldAccess.readable.`);
206
+ throw new TypeError(`${context} requires resource.operations.view.output for fieldAccess.readable.`);
237
207
  }
238
208
 
239
209
  return allowedFields;