ochre-sdk 0.20.11 → 0.20.13

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.d.mts CHANGED
@@ -643,6 +643,26 @@ type PropertyValueQueryItem = {
643
643
  content: string | number | boolean | null;
644
644
  label: string | null;
645
645
  };
646
+ /**
647
+ * Represents sorting direction for Set items
648
+ */
649
+ type SetItemsSortDirection = "asc" | "desc";
650
+ /**
651
+ * Represents sorting options for Set items
652
+ */
653
+ type SetItemsSort = {
654
+ target: "none";
655
+ } | {
656
+ target: "title";
657
+ direction?: SetItemsSortDirection;
658
+ language?: string;
659
+ } | {
660
+ target: "propertyValue";
661
+ propertyVariableUuid: string;
662
+ dataType: Exclude<PropertyValueContentType, "coordinate">;
663
+ direction?: SetItemsSortDirection;
664
+ language?: string;
665
+ };
646
666
  /**
647
667
  * Represents a query for Set items
648
668
  */
@@ -1198,6 +1218,8 @@ declare function fetchItem<T extends DataCategory = DataCategory, U extends Data
1198
1218
  * @param params.belongsToCollectionScopeUuids - The collection scope UUIDs to filter by
1199
1219
  * @param params.propertyVariableUuids - The property variable UUIDs to filter by
1200
1220
  * @param params.queries - Ordered queries to combine with AND/OR and optional NOT via negation
1221
+ * @param params.sort - Optional sorting configuration applied before pagination.
1222
+ * For propertyValue sorting, dataType is required and the sort key uses the first valid leaf value (value[not(@i)]).
1201
1223
  * @param params.page - The page number (1-indexed)
1202
1224
  * @param params.pageSize - The number of items per page
1203
1225
  * @param itemCategories - The categories of the items to fetch
@@ -1211,6 +1233,7 @@ declare function fetchSetItems<U extends Array<DataCategory> = Array<DataCategor
1211
1233
  belongsToCollectionScopeUuids: Array<string>;
1212
1234
  propertyVariableUuids: Array<string>;
1213
1235
  queries: Array<Query>;
1236
+ sort?: SetItemsSort;
1214
1237
  page: number;
1215
1238
  pageSize?: number;
1216
1239
  }, itemCategories?: U, options?: {
@@ -1423,4 +1446,4 @@ declare const DEFAULT_PAGE_SIZE = 48;
1423
1446
  */
1424
1447
  declare function flattenItemProperties<T extends DataCategory = DataCategory, U extends DataCategory | Array<DataCategory> = (T extends "tree" ? Exclude<DataCategory, "tree"> : T extends "set" ? Array<DataCategory> : never)>(item: Item<T, U>): Item<T, U>;
1425
1448
  //#endregion
1426
- export { ApiVersion, Bibliography, Concept, Context, ContextItem, ContextNode, Coordinate, DEFAULT_API_VERSION, DEFAULT_PAGE_SIZE, Data, DataCategory, Event, FileFormat, Gallery, Identification, Image, ImageMap, ImageMapArea, Interpretation, Item, LevelContext, LevelContextItem, License, Link, Metadata, Note, Observation, Period, Person, Property, PropertyContexts, PropertyValue, PropertyValueContent, PropertyValueContentType, PropertyValueQueryItem, PropertyVariable, Query, Resource, Scope, Section, Set, SpatialUnit, Style, Text, Tree, WebBlock, WebBlockLayout, WebElement, WebElementComponent, WebImage, WebSegment, WebSegmentItem, WebTitle, Webpage, Website, WebsiteType, fetchGallery, fetchItem, fetchSetItems, fetchSetPropertyValuesByPropertyVariables, fetchWebsite, filterProperties, flattenItemProperties, getLeafPropertyValues, getPropertyByLabel, getPropertyByLabelAndValue, getPropertyByLabelAndValues, getPropertyByUuid, getPropertyValueByLabel, getPropertyValueByUuid, getPropertyValuesByLabel, getPropertyValuesByUuid, getUniqueProperties, getUniquePropertyLabels };
1449
+ export { ApiVersion, Bibliography, Concept, Context, ContextItem, ContextNode, Coordinate, DEFAULT_API_VERSION, DEFAULT_PAGE_SIZE, Data, DataCategory, Event, FileFormat, Gallery, Identification, Image, ImageMap, ImageMapArea, Interpretation, Item, LevelContext, LevelContextItem, License, Link, Metadata, Note, Observation, Period, Person, Property, PropertyContexts, PropertyValue, PropertyValueContent, PropertyValueContentType, PropertyValueQueryItem, PropertyVariable, Query, Resource, Scope, Section, Set, SetItemsSort, SetItemsSortDirection, SpatialUnit, Style, Text, Tree, WebBlock, WebBlockLayout, WebElement, WebElementComponent, WebImage, WebSegment, WebSegmentItem, WebTitle, Webpage, Website, WebsiteType, fetchGallery, fetchItem, fetchSetItems, fetchSetPropertyValuesByPropertyVariables, fetchWebsite, filterProperties, flattenItemProperties, getLeafPropertyValues, getPropertyByLabel, getPropertyByLabelAndValue, getPropertyByLabelAndValues, getPropertyByUuid, getPropertyValueByLabel, getPropertyValueByUuid, getPropertyValuesByLabel, getPropertyValuesByUuid, getUniqueProperties, getUniquePropertyLabels };
package/dist/index.mjs CHANGED
@@ -831,27 +831,40 @@ const setQuerySchema = z.union([
831
831
  }).strict()
832
832
  ]);
833
833
  const setQueriesSchema = z.array(setQuerySchema).default([]);
834
+ const setItemsSortSchema = z.discriminatedUnion("target", [
835
+ z.object({ target: z.literal("none") }).strict(),
836
+ z.object({
837
+ target: z.literal("title"),
838
+ direction: z.enum(["asc", "desc"]).default("asc"),
839
+ language: z.string().default("eng")
840
+ }).strict(),
841
+ z.object({
842
+ target: z.literal("propertyValue"),
843
+ propertyVariableUuid: uuidSchema,
844
+ dataType: z.enum([
845
+ "string",
846
+ "integer",
847
+ "decimal",
848
+ "boolean",
849
+ "date",
850
+ "dateTime",
851
+ "time",
852
+ "IDREF"
853
+ ]),
854
+ direction: z.enum(["asc", "desc"]).default("asc"),
855
+ language: z.string().default("eng")
856
+ }).strict()
857
+ ]).default({ target: "none" });
834
858
  function validateSetQueriesOperators(queries, ctx) {
835
- for (const [index, query] of queries.entries()) {
836
- if (index === 0 && query.operator != null) ctx.addIssue({
837
- code: "custom",
838
- path: [
839
- "queries",
840
- index,
841
- "operator"
842
- ],
843
- message: "The first query rule must not include an operator"
844
- });
845
- if (index > 0 && query.operator == null) ctx.addIssue({
846
- code: "custom",
847
- path: [
848
- "queries",
849
- index,
850
- "operator"
851
- ],
852
- message: "Query rules after the first must include an operator"
853
- });
854
- }
859
+ for (const [index, query] of queries.entries()) if (index > 0 && query.operator == null) ctx.addIssue({
860
+ code: "custom",
861
+ path: [
862
+ "queries",
863
+ index,
864
+ "operator"
865
+ ],
866
+ message: "Query rules after the first must include an operator"
867
+ });
855
868
  }
856
869
  /**
857
870
  * Schema for validating the parameters for the Set property values by property variables fetching function
@@ -871,6 +884,7 @@ const setItemsParamsSchema = z.object({
871
884
  belongsToCollectionScopeUuids: z.array(uuidSchema).default([]),
872
885
  propertyVariableUuids: z.array(uuidSchema).default([]),
873
886
  queries: setQueriesSchema,
887
+ sort: setItemsSortSchema,
874
888
  page: z.number().min(1, "Page must be positive").default(1),
875
889
  pageSize: z.number().min(1, "Page size must be positive").default(DEFAULT_PAGE_SIZE)
876
890
  }).superRefine((value, ctx) => {
@@ -2210,12 +2224,82 @@ function buildQueryFilters(queries) {
2210
2224
  return queries.map((query, index) => {
2211
2225
  const clause = buildBooleanQueryClause(query);
2212
2226
  if (index === 0) return clause;
2213
- return `${query.operator === "OR" ? "or" : "and"} ${clause}`;
2227
+ return `${query.operator === "AND" ? "and" : "or"} ${clause}`;
2214
2228
  }).join(" ");
2215
2229
  }
2216
2230
 
2217
2231
  //#endregion
2218
2232
  //#region src/utils/fetchers/set/items.ts
2233
+ function mapSortDirectionToXQuery(direction) {
2234
+ return direction === "desc" ? "descending" : "ascending";
2235
+ }
2236
+ function buildStringOrderByClause(direction) {
2237
+ return `($sortKey = "") ascending, lower-case($sortKey) ${direction}, $position ascending`;
2238
+ }
2239
+ function buildTypedOrderByClause(direction) {
2240
+ return `empty($sortKey) ascending, $sortKey ${direction}, $position ascending`;
2241
+ }
2242
+ function buildPropertyValueValuePath(sort) {
2243
+ return `$item//properties//property[label/@uuid=${stringLiteral(sort.propertyVariableUuid)}]/value[not(@i)]`;
2244
+ }
2245
+ function buildPropertyValueStringSortKeyExpression(sort) {
2246
+ const languageLiteral = stringLiteral(sort.language ?? "eng");
2247
+ return `string((for $v in ${buildPropertyValueValuePath(sort)}
2248
+ let $candidate := string-join($v/content[@xml:lang=${languageLiteral}]/string, "")
2249
+ where string-length($candidate) gt 0
2250
+ return $candidate)[1])`;
2251
+ }
2252
+ function buildPropertyValueTypedSortKeyExpression(params) {
2253
+ const { sort, dataType } = params;
2254
+ const propertyValuePath = buildPropertyValueValuePath(sort);
2255
+ switch (dataType) {
2256
+ case "integer": return `(for $v in ${propertyValuePath}
2257
+ let $candidate := normalize-space(string($v/@rawValue))
2258
+ where $candidate castable as xs:integer
2259
+ return xs:integer($candidate))[1]`;
2260
+ case "decimal":
2261
+ case "time": return `(for $v in ${propertyValuePath}
2262
+ let $candidate := normalize-space(string($v/@rawValue))
2263
+ where $candidate castable as xs:decimal
2264
+ return xs:decimal($candidate))[1]`;
2265
+ case "boolean": return `(for $v in ${propertyValuePath}
2266
+ let $candidate := lower-case(normalize-space(string($v/@rawValue)))
2267
+ where $candidate = ("true", "false", "1", "0")
2268
+ return if ($candidate = ("true", "1")) then 1 else 0)[1]`;
2269
+ case "date": return `(for $v in ${propertyValuePath}
2270
+ let $candidate := normalize-space(string($v/@rawValue))
2271
+ where $candidate castable as xs:date
2272
+ return xs:date($candidate))[1]`;
2273
+ case "dateTime": return `(for $v in ${propertyValuePath}
2274
+ let $candidate := normalize-space(string($v/@rawValue))
2275
+ where $candidate castable as xs:dateTime
2276
+ return xs:dateTime($candidate))[1]`;
2277
+ }
2278
+ }
2279
+ function buildPropertyValueOrderByClause(params) {
2280
+ const { dataType, direction } = params;
2281
+ return dataType === "string" || dataType === "IDREF" ? buildStringOrderByClause(direction) : buildTypedOrderByClause(direction);
2282
+ }
2283
+ function buildOrderedItemsClause(sort) {
2284
+ if (sort.target === "none") return "let $orderedItems := $items";
2285
+ const direction = mapSortDirectionToXQuery(sort.direction);
2286
+ if (sort.target === "title") return `let $orderedItems :=
2287
+ for $item at $position in $items
2288
+ let $sortKey := ${`string-join($item/identification/label/content[@xml:lang=${stringLiteral(sort.language ?? "eng")}]/string, "")`}
2289
+ stable order by ${buildStringOrderByClause(direction)}
2290
+ return $item`;
2291
+ return `let $orderedItems :=
2292
+ for $item at $position in $items
2293
+ let $sortKey := ${sort.dataType === "string" || sort.dataType === "IDREF" ? buildPropertyValueStringSortKeyExpression(sort) : buildPropertyValueTypedSortKeyExpression({
2294
+ sort,
2295
+ dataType: sort.dataType
2296
+ })}
2297
+ stable order by ${buildPropertyValueOrderByClause({
2298
+ dataType: sort.dataType,
2299
+ direction
2300
+ })}
2301
+ return $item`;
2302
+ }
2219
2303
  /**
2220
2304
  * Build an XQuery string to fetch Set items from the OCHRE API
2221
2305
  * @param params - The parameters for the fetch
@@ -2223,6 +2307,8 @@ function buildQueryFilters(queries) {
2223
2307
  * @param params.belongsToCollectionScopeUuids - An array of collection scope UUIDs to filter by
2224
2308
  * @param params.propertyVariableUuids - An array of property variable UUIDs to filter by
2225
2309
  * @param params.queries - Ordered queries to combine with AND/OR and optional NOT via negation
2310
+ * @param params.sort - Optional sorting configuration applied before pagination.
2311
+ * For propertyValue sorting, dataType is required and the sort key uses the first valid leaf value (value[not(@i)]).
2226
2312
  * @param params.page - The page number (1-indexed)
2227
2313
  * @param params.pageSize - The number of items per page
2228
2314
  * @param options - Options for the fetch
@@ -2231,7 +2317,7 @@ function buildQueryFilters(queries) {
2231
2317
  */
2232
2318
  function buildXQuery$1(params, options) {
2233
2319
  const version = options?.version ?? DEFAULT_API_VERSION;
2234
- const { propertyVariableUuids, queries, setScopeUuids, belongsToCollectionScopeUuids, page, pageSize } = params;
2320
+ const { propertyVariableUuids, queries, sort, setScopeUuids, belongsToCollectionScopeUuids, page, pageSize } = params;
2235
2321
  const startPosition = (page - 1) * pageSize + 1;
2236
2322
  const endPosition = page * pageSize;
2237
2323
  const setScopeFilter = `/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`;
@@ -2247,14 +2333,16 @@ function buildXQuery$1(params, options) {
2247
2333
  }
2248
2334
  if (queryFilters.length > 0) filterPredicates.push(`(${queryFilters})`);
2249
2335
  const itemFilters = filterPredicates.length > 0 ? `[${filterPredicates.join(" and ")}]` : "";
2336
+ const orderedItemsClause = buildOrderedItemsClause(sort);
2250
2337
  return `<ochre>{${`let $items := ${version === 2 ? "doc()" : "input()"}/ochre
2251
2338
  ${setScopeFilter}
2252
2339
  ${itemFilters}
2253
2340
 
2254
2341
  let $totalCount := count($items)
2342
+ ${orderedItemsClause}
2255
2343
 
2256
2344
  return <items totalCount="{$totalCount}" page="${page}" pageSize="${pageSize}">{
2257
- for $item in $items[position() ge ${startPosition} and position() le ${endPosition}]
2345
+ for $item in $orderedItems[position() ge ${startPosition} and position() le ${endPosition}]
2258
2346
  return element { node-name($item) } {
2259
2347
  $item/@*, $item/node()
2260
2348
  }
@@ -2268,6 +2356,8 @@ function buildXQuery$1(params, options) {
2268
2356
  * @param params.belongsToCollectionScopeUuids - The collection scope UUIDs to filter by
2269
2357
  * @param params.propertyVariableUuids - The property variable UUIDs to filter by
2270
2358
  * @param params.queries - Ordered queries to combine with AND/OR and optional NOT via negation
2359
+ * @param params.sort - Optional sorting configuration applied before pagination.
2360
+ * For propertyValue sorting, dataType is required and the sort key uses the first valid leaf value (value[not(@i)]).
2271
2361
  * @param params.page - The page number (1-indexed)
2272
2362
  * @param params.pageSize - The number of items per page
2273
2363
  * @param itemCategories - The categories of the items to fetch
@@ -2279,12 +2369,13 @@ function buildXQuery$1(params, options) {
2279
2369
  async function fetchSetItems(params, itemCategories, options) {
2280
2370
  try {
2281
2371
  const version = options?.version ?? DEFAULT_API_VERSION;
2282
- const { setScopeUuids, belongsToCollectionScopeUuids, propertyVariableUuids, queries, page, pageSize } = setItemsParamsSchema.parse(params);
2372
+ const { setScopeUuids, belongsToCollectionScopeUuids, propertyVariableUuids, queries, sort, page, pageSize } = setItemsParamsSchema.parse(params);
2283
2373
  const xquery = buildXQuery$1({
2284
2374
  setScopeUuids,
2285
2375
  belongsToCollectionScopeUuids,
2286
2376
  propertyVariableUuids,
2287
2377
  queries,
2378
+ sort,
2288
2379
  page,
2289
2380
  pageSize
2290
2381
  }, { version });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ochre-sdk",
3
- "version": "0.20.11",
3
+ "version": "0.20.13",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Node.js library for working with OCHRE (Online Cultural and Historical Research Environment) data",