ochre-sdk 0.20.12 → 0.20.14

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,6 +831,30 @@ 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
859
  for (const [index, query] of queries.entries()) if (index > 0 && query.operator == null) ctx.addIssue({
836
860
  code: "custom",
@@ -860,6 +884,7 @@ const setItemsParamsSchema = z.object({
860
884
  belongsToCollectionScopeUuids: z.array(uuidSchema).default([]),
861
885
  propertyVariableUuids: z.array(uuidSchema).default([]),
862
886
  queries: setQueriesSchema,
887
+ sort: setItemsSortSchema,
863
888
  page: z.number().min(1, "Page must be positive").default(1),
864
889
  pageSize: z.number().min(1, "Page size must be positive").default(DEFAULT_PAGE_SIZE)
865
890
  }).superRefine((value, ctx) => {
@@ -2199,12 +2224,82 @@ function buildQueryFilters(queries) {
2199
2224
  return queries.map((query, index) => {
2200
2225
  const clause = buildBooleanQueryClause(query);
2201
2226
  if (index === 0) return clause;
2202
- return `${query.operator === "OR" ? "or" : "and"} ${clause}`;
2227
+ return `${query.operator === "AND" ? "and" : "or"} ${clause}`;
2203
2228
  }).join(" ");
2204
2229
  }
2205
2230
 
2206
2231
  //#endregion
2207
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
+ }
2208
2303
  /**
2209
2304
  * Build an XQuery string to fetch Set items from the OCHRE API
2210
2305
  * @param params - The parameters for the fetch
@@ -2212,6 +2307,8 @@ function buildQueryFilters(queries) {
2212
2307
  * @param params.belongsToCollectionScopeUuids - An array of collection scope UUIDs to filter by
2213
2308
  * @param params.propertyVariableUuids - An array of property variable UUIDs to filter by
2214
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)]).
2215
2312
  * @param params.page - The page number (1-indexed)
2216
2313
  * @param params.pageSize - The number of items per page
2217
2314
  * @param options - Options for the fetch
@@ -2220,7 +2317,7 @@ function buildQueryFilters(queries) {
2220
2317
  */
2221
2318
  function buildXQuery$1(params, options) {
2222
2319
  const version = options?.version ?? DEFAULT_API_VERSION;
2223
- const { propertyVariableUuids, queries, setScopeUuids, belongsToCollectionScopeUuids, page, pageSize } = params;
2320
+ const { propertyVariableUuids, queries, sort, setScopeUuids, belongsToCollectionScopeUuids, page, pageSize } = params;
2224
2321
  const startPosition = (page - 1) * pageSize + 1;
2225
2322
  const endPosition = page * pageSize;
2226
2323
  const setScopeFilter = `/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`;
@@ -2236,14 +2333,16 @@ function buildXQuery$1(params, options) {
2236
2333
  }
2237
2334
  if (queryFilters.length > 0) filterPredicates.push(`(${queryFilters})`);
2238
2335
  const itemFilters = filterPredicates.length > 0 ? `[${filterPredicates.join(" and ")}]` : "";
2336
+ const orderedItemsClause = buildOrderedItemsClause(sort);
2239
2337
  return `<ochre>{${`let $items := ${version === 2 ? "doc()" : "input()"}/ochre
2240
2338
  ${setScopeFilter}
2241
2339
  ${itemFilters}
2242
2340
 
2243
2341
  let $totalCount := count($items)
2342
+ ${orderedItemsClause}
2244
2343
 
2245
2344
  return <items totalCount="{$totalCount}" page="${page}" pageSize="${pageSize}">{
2246
- for $item in $items[position() ge ${startPosition} and position() le ${endPosition}]
2345
+ for $item in $orderedItems[position() ge ${startPosition} and position() le ${endPosition}]
2247
2346
  return element { node-name($item) } {
2248
2347
  $item/@*, $item/node()
2249
2348
  }
@@ -2257,6 +2356,8 @@ function buildXQuery$1(params, options) {
2257
2356
  * @param params.belongsToCollectionScopeUuids - The collection scope UUIDs to filter by
2258
2357
  * @param params.propertyVariableUuids - The property variable UUIDs to filter by
2259
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)]).
2260
2361
  * @param params.page - The page number (1-indexed)
2261
2362
  * @param params.pageSize - The number of items per page
2262
2363
  * @param itemCategories - The categories of the items to fetch
@@ -2268,12 +2369,13 @@ function buildXQuery$1(params, options) {
2268
2369
  async function fetchSetItems(params, itemCategories, options) {
2269
2370
  try {
2270
2371
  const version = options?.version ?? DEFAULT_API_VERSION;
2271
- const { setScopeUuids, belongsToCollectionScopeUuids, propertyVariableUuids, queries, page, pageSize } = setItemsParamsSchema.parse(params);
2372
+ const { setScopeUuids, belongsToCollectionScopeUuids, propertyVariableUuids, queries, sort, page, pageSize } = setItemsParamsSchema.parse(params);
2272
2373
  const xquery = buildXQuery$1({
2273
2374
  setScopeUuids,
2274
2375
  belongsToCollectionScopeUuids,
2275
2376
  propertyVariableUuids,
2276
2377
  queries,
2378
+ sort,
2277
2379
  page,
2278
2380
  pageSize
2279
2381
  }, { version });
@@ -2440,7 +2542,7 @@ const propertyValueQueryItemSchema = z.object({
2440
2542
  returnValue.label = parsePropertyValueLabel(val.content);
2441
2543
  break;
2442
2544
  default:
2443
- returnValue.content = val.rawValue != null && val.rawValue !== "" ? val.rawValue.toString() : null;
2545
+ returnValue.content = val.rawValue != null && val.rawValue !== "" ? val.rawValue.toString() : val.content != null ? parseStringContent({ content: val.content }) : null;
2444
2546
  returnValue.label = parsePropertyValueLabel(val.content);
2445
2547
  break;
2446
2548
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ochre-sdk",
3
- "version": "0.20.12",
3
+ "version": "0.20.14",
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",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@antfu/eslint-config": "^7.6.1",
50
- "@types/node": "^24.10.15",
50
+ "@types/node": "^24.11.0",
51
51
  "bumpp": "^10.4.1",
52
52
  "eslint": "^10.0.2",
53
53
  "prettier": "^3.8.1",
@@ -57,12 +57,10 @@
57
57
  "scripts": {
58
58
  "dev": "tsdown src/index.ts --watch",
59
59
  "build": "tsdown",
60
- "lint": "eslint --cache .",
61
- "lint:fix": "eslint --cache . --fix",
62
- "lint:nocache": "eslint .",
63
- "lint:nocache:fix": "eslint . --fix",
64
- "format": "prettier --cache --check .",
65
- "format:fix": "prettier --cache --write --list-different .",
60
+ "lint": "eslint .",
61
+ "lint:fix": "eslint . --fix",
62
+ "format": "prettier --check .",
63
+ "format:fix": "prettier --write --list-different .",
66
64
  "check-types": "tsc --noEmit",
67
65
  "release": "bumpp"
68
66
  }