@nubitio/hydra 0.5.20 → 0.5.23

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/index.cjs CHANGED
@@ -93,12 +93,24 @@ function getFlatIriFilter(filter) {
93
93
  if (!Array.isArray(filter)) return null;
94
94
  return filter.length === 1 && Array.isArray(filter[0]) ? filter[0] : filter;
95
95
  }
96
+ function parseGridSummaryHeader(headers) {
97
+ const raw = headers.get("x-grid-summary");
98
+ if (!raw) return null;
99
+ try {
100
+ const parsed = JSON.parse(raw);
101
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
96
106
  function parseCollectionResponse(responseData, responseHeaders) {
107
+ const gridSummary = parseGridSummaryHeader(responseHeaders);
97
108
  if (Array.isArray(responseData)) {
98
109
  const headerCount = Number(responseHeaders.get("x-total-count"));
99
110
  return {
100
111
  data: responseData,
101
- totalCount: Number.isFinite(headerCount) ? headerCount : responseData.length
112
+ totalCount: Number.isFinite(headerCount) ? headerCount : responseData.length,
113
+ gridSummary
102
114
  };
103
115
  }
104
116
  if (responseData && typeof responseData === "object") {
@@ -109,13 +121,15 @@ function parseCollectionResponse(responseData, responseHeaders) {
109
121
  const totalCount = Number(rawTotal);
110
122
  return {
111
123
  data: member,
112
- totalCount: Number.isFinite(totalCount) ? totalCount : member.length
124
+ totalCount: Number.isFinite(totalCount) ? totalCount : member.length,
125
+ gridSummary
113
126
  };
114
127
  }
115
128
  }
116
129
  return {
117
130
  data: [],
118
- totalCount: 0
131
+ totalCount: 0,
132
+ gridSummary
119
133
  };
120
134
  }
121
135
  var HydraRemoteDataSource = class {
@@ -174,7 +188,8 @@ var HydraRemoteDataSource = class {
174
188
  return {
175
189
  data,
176
190
  totalCount,
177
- summary: null
191
+ summary: null,
192
+ gridSummary: parsed.gridSummary
178
193
  };
179
194
  }
180
195
  async fetchById(url, id, loadOptions) {
@@ -194,6 +209,16 @@ function HydraResourceStoreProvider({ children }) {
194
209
  });
195
210
  }
196
211
  //#endregion
212
+ //#region packages/hydra/buildSummaryFields.ts
213
+ function buildSummaryFieldsFromSchema(schema) {
214
+ return schema.fields.filter((field) => field.crudHints?.summable).map((field) => ({
215
+ column: field.name,
216
+ summaryType: field.crudHints?.summaryType ?? "sum",
217
+ valueFormat: field.crudHints?.format === "currency" ? "currency" : void 0,
218
+ label: field["hydra:title"] && field["hydra:title"] !== field.name ? field["hydra:title"] : void 0
219
+ }));
220
+ }
221
+ //#endregion
197
222
  //#region packages/hydra/openApiParser.ts
198
223
  /**
199
224
  * Converts a camelCase or PascalCase name to dash-case.
@@ -410,6 +435,7 @@ function parseHydraDoc(doc, entrypointHrefs) {
410
435
  fields,
411
436
  formLayout: cls["x-crud-layout"],
412
437
  workflow: cls["x-workflow"],
438
+ sequence: cls["x-sequence"],
413
439
  searchMappings: extractSearchMappings(cls),
414
440
  supportedOperations: Array.from(new Set([...collectionOperationsMap[className] ?? [], ...extractSupportedOperations(cls)]))
415
441
  };
@@ -520,6 +546,18 @@ function applyCrudHints(field, hints) {
520
546
  if (hints.order !== void 0) field.order = hints.order;
521
547
  if (hints.width !== void 0) field.width = hints.width;
522
548
  }
549
+ /**
550
+ * Apply `x-sequence` hints: the server-allocated field is read-only and
551
+ * excluded from create/edit forms (the listener assigns it on POST).
552
+ */
553
+ function applySequenceHints(fields, schema) {
554
+ const sequenceField = schema.sequence?.field;
555
+ if (!sequenceField) return;
556
+ for (const field of fields) if (field.name === sequenceField) {
557
+ field.visibleOnForm = false;
558
+ field.readonly = true;
559
+ }
560
+ }
523
561
  function resolveEntityValueField(relatedSchema) {
524
562
  if (!relatedSchema) return "_iri";
525
563
  const fieldNames = new Set(relatedSchema.fields.map((field) => field.name));
@@ -716,6 +754,7 @@ function mapHydraSchemaToFields(schema, urlLookup, schemaLookup) {
716
754
  };
717
755
  fields.unshift(syntheticId);
718
756
  }
757
+ applySequenceHints(fields, schema);
719
758
  return fields;
720
759
  }
721
760
  //#endregion
@@ -908,7 +947,9 @@ function useResourceSchema(apiUrl) {
908
947
  error: void 0,
909
948
  supportedOperations: resourceSchema.supportedOperations ?? [],
910
949
  formLayout: resourceSchema.formLayout,
911
- workflow: resourceSchema.workflow
950
+ workflow: resourceSchema.workflow,
951
+ sequence: resourceSchema.sequence,
952
+ summaryFields: buildSummaryFieldsFromSchema(resourceSchema)
912
953
  };
913
954
  }, [
914
955
  data,
package/dist/index.d.cts CHANGED
@@ -74,6 +74,10 @@ interface CrudHints {
74
74
  * upload controls that submit the media IRI.
75
75
  */
76
76
  format?: 'currency' | 'image' | 'file';
77
+ /** Include this column in the server-side grid summary footer. */
78
+ summable?: boolean;
79
+ /** Aggregate function for summable columns. @default 'sum' */
80
+ summaryType?: 'sum' | 'count' | 'avg' | 'min' | 'max';
77
81
  }
78
82
  interface HydraSupportedProperty {
79
83
  '@type': 'SupportedProperty';
@@ -133,6 +137,13 @@ interface WorkflowSchema {
133
137
  field: string;
134
138
  transitions: WorkflowTransitionSchema[];
135
139
  }
140
+ interface SequenceSchema {
141
+ field: string;
142
+ name?: string;
143
+ prefix?: string;
144
+ padding?: number;
145
+ scope?: string[];
146
+ }
136
147
  interface HydraClass {
137
148
  '@id': string;
138
149
  '@type': string;
@@ -140,6 +151,8 @@ interface HydraClass {
140
151
  supportedProperty: HydraSupportedProperty[];
141
152
  /** State machine metadata from nubitio/workflow-bundle. */
142
153
  'x-workflow'?: WorkflowSchema;
154
+ /** Auto-numbering metadata from nubitio/sequence-bundle. */
155
+ 'x-sequence'?: SequenceSchema;
143
156
  /**
144
157
  * Class-level UI hints injected by TranslatedDocumentationNormalizer from
145
158
  * the ApiResource's extraProperties['x-crud']['formLayout']. Mirrors
@@ -234,6 +247,8 @@ interface HydraResourceSchema {
234
247
  supportedOperations?: string[];
235
248
  /** Workflow transitions for auto row actions (nubitio/workflow-bundle). */
236
249
  workflow?: WorkflowSchema;
250
+ /** Server-allocated sequence field (nubitio/sequence-bundle). */
251
+ sequence?: SequenceSchema;
237
252
  }
238
253
  interface OpenApiProperty {
239
254
  type?: string;
@@ -384,4 +399,4 @@ interface UseSchemaContextResult {
384
399
  */
385
400
  declare function useSchemaContext(): UseSchemaContextResult;
386
401
  //#endregion
387
- export { API_DOC_QUERY_KEY, type ApiDoc, type HydraApiDoc, HydraRemoteDataSource, HydraResourceSchemaProvider, type HydraResourceSchemaProviderProps, HydraResourceStoreProvider, type HydraResourceStoreProviderProps, type OpenApiDoc, type RemoteDataSourceOptions, type RemoteFilterDescriptor, type RemoteLoadOptions, type RemoteSortDescriptor, SchemaProvider, type UseSchemaContextResult, type WorkflowSchema, type WorkflowTransitionSchema, createHydraResourceStore, mapHydraSchemaToFields, parseHydraDoc, parseOpenApiDoc, resolveRangeTag, useHydraMetadata, useResourceSchema, useSchemaContext };
402
+ export { API_DOC_QUERY_KEY, type ApiDoc, type HydraApiDoc, HydraRemoteDataSource, HydraResourceSchemaProvider, type HydraResourceSchemaProviderProps, HydraResourceStoreProvider, type HydraResourceStoreProviderProps, type OpenApiDoc, type RemoteDataSourceOptions, type RemoteFilterDescriptor, type RemoteLoadOptions, type RemoteSortDescriptor, SchemaProvider, type SequenceSchema, type UseSchemaContextResult, type WorkflowSchema, type WorkflowTransitionSchema, createHydraResourceStore, mapHydraSchemaToFields, parseHydraDoc, parseOpenApiDoc, resolveRangeTag, useHydraMetadata, useResourceSchema, useSchemaContext };
package/dist/index.d.mts CHANGED
@@ -74,6 +74,10 @@ interface CrudHints {
74
74
  * upload controls that submit the media IRI.
75
75
  */
76
76
  format?: 'currency' | 'image' | 'file';
77
+ /** Include this column in the server-side grid summary footer. */
78
+ summable?: boolean;
79
+ /** Aggregate function for summable columns. @default 'sum' */
80
+ summaryType?: 'sum' | 'count' | 'avg' | 'min' | 'max';
77
81
  }
78
82
  interface HydraSupportedProperty {
79
83
  '@type': 'SupportedProperty';
@@ -133,6 +137,13 @@ interface WorkflowSchema {
133
137
  field: string;
134
138
  transitions: WorkflowTransitionSchema[];
135
139
  }
140
+ interface SequenceSchema {
141
+ field: string;
142
+ name?: string;
143
+ prefix?: string;
144
+ padding?: number;
145
+ scope?: string[];
146
+ }
136
147
  interface HydraClass {
137
148
  '@id': string;
138
149
  '@type': string;
@@ -140,6 +151,8 @@ interface HydraClass {
140
151
  supportedProperty: HydraSupportedProperty[];
141
152
  /** State machine metadata from nubitio/workflow-bundle. */
142
153
  'x-workflow'?: WorkflowSchema;
154
+ /** Auto-numbering metadata from nubitio/sequence-bundle. */
155
+ 'x-sequence'?: SequenceSchema;
143
156
  /**
144
157
  * Class-level UI hints injected by TranslatedDocumentationNormalizer from
145
158
  * the ApiResource's extraProperties['x-crud']['formLayout']. Mirrors
@@ -234,6 +247,8 @@ interface HydraResourceSchema {
234
247
  supportedOperations?: string[];
235
248
  /** Workflow transitions for auto row actions (nubitio/workflow-bundle). */
236
249
  workflow?: WorkflowSchema;
250
+ /** Server-allocated sequence field (nubitio/sequence-bundle). */
251
+ sequence?: SequenceSchema;
237
252
  }
238
253
  interface OpenApiProperty {
239
254
  type?: string;
@@ -384,4 +399,4 @@ interface UseSchemaContextResult {
384
399
  */
385
400
  declare function useSchemaContext(): UseSchemaContextResult;
386
401
  //#endregion
387
- export { API_DOC_QUERY_KEY, type ApiDoc, type HydraApiDoc, HydraRemoteDataSource, HydraResourceSchemaProvider, type HydraResourceSchemaProviderProps, HydraResourceStoreProvider, type HydraResourceStoreProviderProps, type OpenApiDoc, type RemoteDataSourceOptions, type RemoteFilterDescriptor, type RemoteLoadOptions, type RemoteSortDescriptor, SchemaProvider, type UseSchemaContextResult, type WorkflowSchema, type WorkflowTransitionSchema, createHydraResourceStore, mapHydraSchemaToFields, parseHydraDoc, parseOpenApiDoc, resolveRangeTag, useHydraMetadata, useResourceSchema, useSchemaContext };
402
+ export { API_DOC_QUERY_KEY, type ApiDoc, type HydraApiDoc, HydraRemoteDataSource, HydraResourceSchemaProvider, type HydraResourceSchemaProviderProps, HydraResourceStoreProvider, type HydraResourceStoreProviderProps, type OpenApiDoc, type RemoteDataSourceOptions, type RemoteFilterDescriptor, type RemoteLoadOptions, type RemoteSortDescriptor, SchemaProvider, type SequenceSchema, type UseSchemaContextResult, type WorkflowSchema, type WorkflowTransitionSchema, createHydraResourceStore, mapHydraSchemaToFields, parseHydraDoc, parseOpenApiDoc, resolveRangeTag, useHydraMetadata, useResourceSchema, useSchemaContext };
package/dist/index.mjs CHANGED
@@ -69,12 +69,24 @@ function getFlatIriFilter(filter) {
69
69
  if (!Array.isArray(filter)) return null;
70
70
  return filter.length === 1 && Array.isArray(filter[0]) ? filter[0] : filter;
71
71
  }
72
+ function parseGridSummaryHeader(headers) {
73
+ const raw = headers.get("x-grid-summary");
74
+ if (!raw) return null;
75
+ try {
76
+ const parsed = JSON.parse(raw);
77
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
72
82
  function parseCollectionResponse(responseData, responseHeaders) {
83
+ const gridSummary = parseGridSummaryHeader(responseHeaders);
73
84
  if (Array.isArray(responseData)) {
74
85
  const headerCount = Number(responseHeaders.get("x-total-count"));
75
86
  return {
76
87
  data: responseData,
77
- totalCount: Number.isFinite(headerCount) ? headerCount : responseData.length
88
+ totalCount: Number.isFinite(headerCount) ? headerCount : responseData.length,
89
+ gridSummary
78
90
  };
79
91
  }
80
92
  if (responseData && typeof responseData === "object") {
@@ -85,13 +97,15 @@ function parseCollectionResponse(responseData, responseHeaders) {
85
97
  const totalCount = Number(rawTotal);
86
98
  return {
87
99
  data: member,
88
- totalCount: Number.isFinite(totalCount) ? totalCount : member.length
100
+ totalCount: Number.isFinite(totalCount) ? totalCount : member.length,
101
+ gridSummary
89
102
  };
90
103
  }
91
104
  }
92
105
  return {
93
106
  data: [],
94
- totalCount: 0
107
+ totalCount: 0,
108
+ gridSummary
95
109
  };
96
110
  }
97
111
  var HydraRemoteDataSource = class {
@@ -150,7 +164,8 @@ var HydraRemoteDataSource = class {
150
164
  return {
151
165
  data,
152
166
  totalCount,
153
- summary: null
167
+ summary: null,
168
+ gridSummary: parsed.gridSummary
154
169
  };
155
170
  }
156
171
  async fetchById(url, id, loadOptions) {
@@ -170,6 +185,16 @@ function HydraResourceStoreProvider({ children }) {
170
185
  });
171
186
  }
172
187
  //#endregion
188
+ //#region packages/hydra/buildSummaryFields.ts
189
+ function buildSummaryFieldsFromSchema(schema) {
190
+ return schema.fields.filter((field) => field.crudHints?.summable).map((field) => ({
191
+ column: field.name,
192
+ summaryType: field.crudHints?.summaryType ?? "sum",
193
+ valueFormat: field.crudHints?.format === "currency" ? "currency" : void 0,
194
+ label: field["hydra:title"] && field["hydra:title"] !== field.name ? field["hydra:title"] : void 0
195
+ }));
196
+ }
197
+ //#endregion
173
198
  //#region packages/hydra/openApiParser.ts
174
199
  /**
175
200
  * Converts a camelCase or PascalCase name to dash-case.
@@ -386,6 +411,7 @@ function parseHydraDoc(doc, entrypointHrefs) {
386
411
  fields,
387
412
  formLayout: cls["x-crud-layout"],
388
413
  workflow: cls["x-workflow"],
414
+ sequence: cls["x-sequence"],
389
415
  searchMappings: extractSearchMappings(cls),
390
416
  supportedOperations: Array.from(new Set([...collectionOperationsMap[className] ?? [], ...extractSupportedOperations(cls)]))
391
417
  };
@@ -496,6 +522,18 @@ function applyCrudHints(field, hints) {
496
522
  if (hints.order !== void 0) field.order = hints.order;
497
523
  if (hints.width !== void 0) field.width = hints.width;
498
524
  }
525
+ /**
526
+ * Apply `x-sequence` hints: the server-allocated field is read-only and
527
+ * excluded from create/edit forms (the listener assigns it on POST).
528
+ */
529
+ function applySequenceHints(fields, schema) {
530
+ const sequenceField = schema.sequence?.field;
531
+ if (!sequenceField) return;
532
+ for (const field of fields) if (field.name === sequenceField) {
533
+ field.visibleOnForm = false;
534
+ field.readonly = true;
535
+ }
536
+ }
499
537
  function resolveEntityValueField(relatedSchema) {
500
538
  if (!relatedSchema) return "_iri";
501
539
  const fieldNames = new Set(relatedSchema.fields.map((field) => field.name));
@@ -692,6 +730,7 @@ function mapHydraSchemaToFields(schema, urlLookup, schemaLookup) {
692
730
  };
693
731
  fields.unshift(syntheticId);
694
732
  }
733
+ applySequenceHints(fields, schema);
695
734
  return fields;
696
735
  }
697
736
  //#endregion
@@ -884,7 +923,9 @@ function useResourceSchema(apiUrl) {
884
923
  error: void 0,
885
924
  supportedOperations: resourceSchema.supportedOperations ?? [],
886
925
  formLayout: resourceSchema.formLayout,
887
- workflow: resourceSchema.workflow
926
+ workflow: resourceSchema.workflow,
927
+ sequence: resourceSchema.sequence,
928
+ summaryFields: buildSummaryFieldsFromSchema(resourceSchema)
888
929
  };
889
930
  }, [
890
931
  data,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubitio/hydra",
3
- "version": "0.5.20",
3
+ "version": "0.5.23",
4
4
  "type": "module",
5
5
  "description": "Hydra / OpenAPI adapter for @nubitio/crud — automatic schema discovery and data source from API Platform docs.",
6
6
  "license": "MIT",
@@ -50,7 +50,7 @@
50
50
  "react": "^19.0.0",
51
51
  "react-dom": "^19.0.0",
52
52
  "react-i18next": "^14.0.0",
53
- "@nubitio/core": "^0.5.20",
54
- "@nubitio/crud": "^0.5.20"
53
+ "@nubitio/core": "^0.5.23",
54
+ "@nubitio/crud": "^0.5.23"
55
55
  }
56
56
  }