@voxgig/sdkgen 4.23.0 → 4.24.0
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/bin/voxgig-sdkgen +18 -2
- package/dist/action/doctor.d.ts +1 -1
- package/dist/action/doctor.js +12 -10
- package/dist/action/doctor.js.map +1 -1
- package/dist/action/edition.js +4 -0
- package/dist/action/edition.js.map +1 -1
- package/dist/action/feature.js +5 -1
- package/dist/action/feature.js.map +1 -1
- package/dist/action/remove.d.ts +16 -0
- package/dist/action/remove.js +345 -0
- package/dist/action/remove.js.map +1 -0
- package/dist/action/target.js +6 -1
- package/dist/action/target.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/project/.sdk/tm/c/core/context.c +3 -1
- package/project/.sdk/tm/c/feature/paging.c +37 -3
- package/project/.sdk/tm/c/feature/test.c +51 -27
- package/project/.sdk/tm/c/utility/prepare_query.c +2 -1
- package/project/.sdk/tm/c/utility/transform_request.c +25 -3
- package/project/.sdk/tm/clojure/src/sdk/core.clj +21 -7
- package/project/.sdk/tm/clojure/src/sdk/features.clj +68 -18
- package/project/.sdk/tm/cpp/core/types.hpp +1 -1
- package/project/.sdk/tm/cpp/feature/paging.hpp +36 -3
- package/project/.sdk/tm/cpp/feature/test.hpp +41 -13
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +16 -4
- package/project/.sdk/tm/csharp/core/Context.cs +1 -1
- package/project/.sdk/tm/csharp/feature/PagingFeature.cs +58 -4
- package/project/.sdk/tm/csharp/feature/TestFeature.cs +13 -21
- package/project/.sdk/tm/csharp/utility/Fetcher.cs +19 -12
- package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +1 -1
- package/project/.sdk/tm/csharp/utility/TransformRequest.cs +23 -3
- package/project/.sdk/tm/elixir/lib/projectname/context.ex +1 -1
- package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +52 -10
- package/project/.sdk/tm/elixir/lib/projectname/feature/test.ex +47 -17
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +30 -9
- package/project/.sdk/tm/go/core/context.go +13 -13
- package/project/.sdk/tm/go/feature/paging_feature.go +40 -2
- package/project/.sdk/tm/go/feature/test_feature.go +3 -15
- package/project/.sdk/tm/go/test/feature_corpus_test.go +1 -0
- package/project/.sdk/tm/go/test/feature_test.go +54 -0
- package/project/.sdk/tm/go/utility/fetcher.go +41 -20
- package/project/.sdk/tm/go/utility/prepare_query.go +1 -1
- package/project/.sdk/tm/go/utility/transform_request.go +22 -3
- package/project/.sdk/tm/java/core/Context.java +2 -1
- package/project/.sdk/tm/java/feature/PagingFeature.java +46 -4
- package/project/.sdk/tm/java/feature/TestFeature.java +10 -13
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +3 -0
- package/project/.sdk/tm/java/test/FeatureTest.java +42 -0
- package/project/.sdk/tm/java/utility/Fetcher.java +36 -28
- package/project/.sdk/tm/java/utility/PrepareQuery.java +1 -1
- package/project/.sdk/tm/java/utility/TransformRequest.java +18 -3
- package/project/.sdk/tm/js/src/Context.js +7 -2
- package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +24 -6
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +7 -5
- package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +1 -1
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +4 -0
- package/project/.sdk/tm/kotlin/core/Context.kt +1 -1
- package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +36 -4
- package/project/.sdk/tm/kotlin/feature/TestFeature.kt +11 -14
- package/project/.sdk/tm/kotlin/utility/Fetcher.kt +33 -24
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +1 -1
- package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -3
- package/project/.sdk/tm/lua/core/context.lua +8 -1
- package/project/.sdk/tm/lua/core/control.lua +3 -0
- package/project/.sdk/tm/lua/feature/paging_feature.lua +44 -5
- package/project/.sdk/tm/lua/feature/test_feature.lua +32 -14
- package/project/.sdk/tm/lua/utility/prepare_query.lua +2 -1
- package/project/.sdk/tm/lua/utility/transform_request.lua +19 -3
- package/project/.sdk/tm/ocaml/sdk_features.ml +72 -21
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +23 -8
- package/project/.sdk/tm/perl/core/context.pm +2 -1
- package/project/.sdk/tm/perl/feature/paging_feature.pm +31 -5
- package/project/.sdk/tm/perl/feature/test_feature.pm +46 -16
- package/project/.sdk/tm/perl/feature/timeout_feature.pm +17 -13
- package/project/.sdk/tm/perl/t/feature_corpus.t +14 -1
- package/project/.sdk/tm/perl/utility/fetcher.pm +15 -1
- package/project/.sdk/tm/perl/utility/prepare_query.pm +1 -0
- package/project/.sdk/tm/perl/utility/transform_request.pm +13 -3
- package/project/.sdk/tm/php/core/Context.php +5 -1
- package/project/.sdk/tm/php/core/Control.php +5 -0
- package/project/.sdk/tm/php/feature/PagingFeature.php +26 -5
- package/project/.sdk/tm/php/feature/TestFeature.php +27 -42
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +17 -1
- package/project/.sdk/tm/php/test/FeatureTest.php +44 -0
- package/project/.sdk/tm/php/utility/Fetcher.php +17 -3
- package/project/.sdk/tm/php/utility/PrepareQuery.php +1 -1
- package/project/.sdk/tm/php/utility/TransformRequest.php +15 -3
- package/project/.sdk/tm/py/pkg/core/context.py +2 -1
- package/project/.sdk/tm/py/pkg/feature/paging_feature.py +29 -4
- package/project/.sdk/tm/py/pkg/feature/test_feature.py +29 -12
- package/project/.sdk/tm/py/pkg/utility/fetcher.py +63 -65
- package/project/.sdk/tm/py/pkg/utility/prepare_query.py +2 -1
- package/project/.sdk/tm/py/pkg/utility/transform_request.py +11 -3
- package/project/.sdk/tm/py/test/test_feature.py +27 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +11 -1
- package/project/.sdk/tm/rb/core/context.rb +1 -1
- package/project/.sdk/tm/rb/feature/paging_feature.rb +27 -5
- package/project/.sdk/tm/rb/feature/test_feature.rb +33 -7
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +12 -1
- package/project/.sdk/tm/rb/test/feature_test.rb +38 -0
- package/project/.sdk/tm/rb/utility/fetcher.rb +49 -3
- package/project/.sdk/tm/rb/utility/prepare_query.rb +1 -1
- package/project/.sdk/tm/rb/utility/transform_request.rb +10 -3
- package/project/.sdk/tm/rust/core/context.rs +2 -6
- package/project/.sdk/tm/rust/feature/paging.rs +44 -3
- package/project/.sdk/tm/rust/feature/secrets.rs +1 -1
- package/project/.sdk/tm/rust/feature/test.rs +66 -33
- package/project/.sdk/tm/rust/utility/fetcher.rs +41 -14
- package/project/.sdk/tm/rust/utility/prepare_query.rs +1 -1
- package/project/.sdk/tm/rust/utility/transform_request.rs +31 -5
- package/project/.sdk/tm/scala/core/Context.scala +2 -1
- package/project/.sdk/tm/scala/feature/PagingFeature.scala +41 -4
- package/project/.sdk/tm/scala/feature/TestFeature.scala +11 -12
- package/project/.sdk/tm/scala/utility/Fetcher.scala +34 -13
- package/project/.sdk/tm/scala/utility/Prepare.scala +1 -1
- package/project/.sdk/tm/scala/utility/Transform.scala +19 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +31 -4
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +12 -9
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +22 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +14 -3
- package/project/.sdk/tm/ts/src/Context.ts +7 -2
- package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +19 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +3 -0
- package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +1 -1
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +1 -0
- package/project/.sdk/tm/zig/core/context.zig +1 -1
- package/project/.sdk/tm/zig/core/utility.zig +19 -5
- package/project/.sdk/tm/zig/feature/paging.zig +34 -4
- package/project/.sdk/tm/zig/feature/test.zig +54 -26
- package/project/sdkgen-package.json +1 -1
- package/src/action/doctor.ts +12 -9
- package/src/action/edition.ts +8 -0
- package/src/action/feature.ts +7 -1
- package/src/action/remove.ts +492 -0
- package/src/action/target.ts +8 -1
|
@@ -1121,9 +1121,15 @@ let paging_feature () : feature =
|
|
|
1121
1121
|
| Noval | Null ->
|
|
1122
1122
|
(match getp q page_param with
|
|
1123
1123
|
| Noval ->
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1124
|
+
(* A record written back by PreResult holds the page just
|
|
1125
|
+
* fetched as `page` and the one to fetch as `nextPage`,
|
|
1126
|
+
* so nextPage wins. *)
|
|
1127
|
+
let page = (match getp paging "nextPage" with
|
|
1128
|
+
| Noval | Null ->
|
|
1129
|
+
(match getp paging "page" with
|
|
1130
|
+
| Noval | Null -> (match getp !options "startPage" with Num n -> Num n | _ -> Num 1.)
|
|
1131
|
+
| p -> p)
|
|
1132
|
+
| np -> np) in
|
|
1127
1133
|
setp q page_param page
|
|
1128
1134
|
| _ -> ())
|
|
1129
1135
|
| c -> setp q cursor_param c);
|
|
@@ -1176,11 +1182,20 @@ let paging_feature () : feature =
|
|
|
1176
1182
|
(match body with
|
|
1177
1183
|
| Map _ ->
|
|
1178
1184
|
(match getp body "next" with Noval | Null -> () | n -> if is_nullish (getp paging "next") then setp paging "next" n);
|
|
1185
|
+
(* Both spellings, camelCase last so it wins when a body
|
|
1186
|
+
* carries the two. *)
|
|
1187
|
+
(match getp body "next_cursor" with Noval | Null -> () | c -> setp paging "cursor" c);
|
|
1179
1188
|
(match getp body "cursor" with Noval | Null -> () | c -> setp paging "cursor" c);
|
|
1180
1189
|
(match getp body "nextCursor" with Noval | Null -> () | c -> setp paging "cursor" c);
|
|
1181
|
-
(
|
|
1182
|
-
|
|
1183
|
-
|
|
1190
|
+
if is_nullish (getp paging "nextPage") then begin
|
|
1191
|
+
let np = (match getp body "nextPage" with
|
|
1192
|
+
| Noval | Null -> getp body "next_page"
|
|
1193
|
+
| v -> v) in
|
|
1194
|
+
(match np with Num _ | Str _ -> setp paging "nextPage" np | _ -> ())
|
|
1195
|
+
end;
|
|
1196
|
+
List.iter (fun k -> match getp body k with
|
|
1197
|
+
| Bool b -> setp paging "hasMore" (Bool b); explicit_more := true
|
|
1198
|
+
| _ -> ()) ["has_more"; "hasMore"]
|
|
1184
1199
|
| _ -> ());
|
|
1185
1200
|
(* Cursor presence only INFERS another page. When the server
|
|
1186
1201
|
* stated the answer outright — relay's `hasNextPage: false`, or
|
|
@@ -1196,6 +1211,16 @@ let paging_feature () : feature =
|
|
|
1196
1211
|
setp paging "hasMore" (Bool hm)
|
|
1197
1212
|
end;
|
|
1198
1213
|
result.rt_paging <- paging;
|
|
1214
|
+
(* The record reached the caller through ctrl in every other typed
|
|
1215
|
+
* port and through nothing here, so a caller who asked for paging
|
|
1216
|
+
* got the pages and never the signals to continue. *)
|
|
1217
|
+
(match ctx.c_ctrl.ctrl_paging with
|
|
1218
|
+
| Map _ as held ->
|
|
1219
|
+
(* The caller holds this map: refill it in place. Every key of
|
|
1220
|
+
* the record is written, so no key of an earlier page lives on. *)
|
|
1221
|
+
List.iter (fun k -> setp held k (getp paging k))
|
|
1222
|
+
["page"; "totalCount"; "nextPage"; "next"; "cursor"; "hasMore"]
|
|
1223
|
+
| _ -> ctx.c_ctrl.ctrl_paging <- paging);
|
|
1199
1224
|
track_set (cc ctx) "paging" (jo [("last", paging)]))
|
|
1200
1225
|
| _ -> ());
|
|
1201
1226
|
f
|
|
@@ -1408,21 +1433,43 @@ let test_feature () : feature =
|
|
|
1408
1433
|
let out = jo [("status", vint_of status); ("statusText", Str "OK"); ("json", json_thunk (envelope ctx data)); ("body", Str "not-used")] in
|
|
1409
1434
|
(match extra with Some (Map _ as e) -> List.iter (fun k -> setp out k (getp e k)) (keysof e) | _ -> ());
|
|
1410
1435
|
(out, None) in
|
|
1436
|
+
let point_terminal p =
|
|
1437
|
+
match getp p "parts" with
|
|
1438
|
+
| List r -> (match List.rev !r with Str s :: _ -> String.length s > 0 && s.[0] = '{' | _ -> false)
|
|
1439
|
+
| _ -> false in
|
|
1440
|
+
let point_depth p = match getp p "parts" with List r -> List.length !r | _ -> 0 in
|
|
1441
|
+
(* The entity's own endpoint: a terminal `{param}` marks a record route, and
|
|
1442
|
+
among equals the shallower path wins (the same rule as make_point). *)
|
|
1443
|
+
let pick_point points =
|
|
1444
|
+
match points with
|
|
1445
|
+
| List r ->
|
|
1446
|
+
(match !r with
|
|
1447
|
+
| [] -> Noval
|
|
1448
|
+
| first :: rest ->
|
|
1449
|
+
List.fold_left (fun point cand ->
|
|
1450
|
+
if point_terminal cand <> point_terminal point then (if point_terminal cand then cand else point)
|
|
1451
|
+
else if point_depth cand < point_depth point then cand
|
|
1452
|
+
else point) first rest)
|
|
1453
|
+
| _ -> Noval in
|
|
1454
|
+
let reqd_names point kind =
|
|
1455
|
+
let reqd_args = select (getpath_s point ("args." ^ kind)) (jo [("reqd", Bool true)]) in
|
|
1456
|
+
transform reqd_args (ja [Str "`$EACH`"; Str ""; Str "`$KEY.name`"]) in
|
|
1411
1457
|
let build_args ctx (op : operation) args =
|
|
1412
1458
|
let opname = op.op_name in
|
|
1413
1459
|
let entname = match ctx.c_entity with Some e -> e.e_name | None -> "_" in
|
|
1414
1460
|
let points = getpath_s ctx.c_config ("entity." ^ entname ^ ".op." ^ opname ^ ".points") in
|
|
1415
|
-
let point =
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
let
|
|
1461
|
+
let point = pick_point points in
|
|
1462
|
+
(* Path AND query: a path-only read misses a query-addressed record
|
|
1463
|
+
(e.g. GET /result?trace_id=), which has no path param at all. *)
|
|
1464
|
+
let reqd_params = reqd_names point "params" in
|
|
1465
|
+
let reqd_query = reqd_names point "query" in
|
|
1419
1466
|
let qand = ref [] in
|
|
1420
1467
|
(match args with
|
|
1421
1468
|
| Map _ ->
|
|
1422
1469
|
List.iter (fun key ->
|
|
1423
1470
|
let is_id = key = "id" in
|
|
1424
|
-
let
|
|
1425
|
-
|
|
1471
|
+
let is_reqd = not (isempty (select reqd_params (Str key)))
|
|
1472
|
+
|| not (isempty (select reqd_query (Str key))) in
|
|
1426
1473
|
if is_id || is_reqd then begin
|
|
1427
1474
|
let v = (cu ctx).u_param ctx (Str key) in
|
|
1428
1475
|
let ka = (match op.op_alias with Map _ -> (match getp op.op_alias key with Str s -> Some s | _ -> None) | _ -> None) in
|
|
@@ -1463,18 +1510,22 @@ let test_feature () : feature =
|
|
|
1463
1510
|
end
|
|
1464
1511
|
| "update" ->
|
|
1465
1512
|
let update_match = empty_map () in
|
|
1466
|
-
(match fctx.c_reqdata with
|
|
1513
|
+
(match fctx.c_reqdata with
|
|
1514
|
+
| Map _ ->
|
|
1515
|
+
(match getp fctx.c_reqdata "id" with Noval -> () | v -> setp update_match "id" v);
|
|
1516
|
+
(match getp op.op_alias "id" with
|
|
1517
|
+
| Str alias_id -> (match getp fctx.c_reqdata alias_id with Noval -> () | v -> setp update_match alias_id v)
|
|
1518
|
+
| _ -> ())
|
|
1519
|
+
| _ -> ());
|
|
1467
1520
|
let update_match = if size update_match > 0 then update_match else resolve_match fctx (empty_map ()) in
|
|
1468
1521
|
let args = build_args fctx op update_match in
|
|
1469
|
-
let ent =
|
|
1470
|
-
(
|
|
1471
|
-
|
|
1472
|
-
| _ -> ());
|
|
1473
|
-
if is_nullish !ent then respond fctx 404 Noval (Some (jo [("statusText", Str "Not found")]))
|
|
1522
|
+
let ent = getelem (select entmap args) (Num 0.0) in
|
|
1523
|
+
(* update miss: 404, never another record *)
|
|
1524
|
+
if is_nullish ent then respond fctx 404 Noval (Some (jo [("statusText", Str "Not found")]))
|
|
1474
1525
|
else begin
|
|
1475
|
-
(match
|
|
1476
|
-
ignore (delprop
|
|
1477
|
-
respond fctx 200 (clone
|
|
1526
|
+
(match ent, fctx.c_reqdata with Map _, Map _ -> ignore (merge (ja [ent; fctx.c_reqdata])) | _ -> ());
|
|
1527
|
+
ignore (delprop ent (Str "$KEY"));
|
|
1528
|
+
respond fctx 200 (clone ent) None
|
|
1478
1529
|
end
|
|
1479
1530
|
| "remove" ->
|
|
1480
1531
|
let args = build_args fctx op (resolve_match fctx fctx.c_reqmatch) in
|
|
@@ -189,7 +189,9 @@ let make_context_impl (cs : ctxspec) (basectx : ctx option) : ctx =
|
|
|
189
189
|
(match getp cr "actor" with Noval -> () | a -> c.ctrl_actor <- a);
|
|
190
190
|
(match getp cr "paging" with Map _ as m -> c.ctrl_paging <- m | _ -> ());
|
|
191
191
|
c
|
|
192
|
-
| _ -> (match basectx with
|
|
192
|
+
| _ -> (match basectx with
|
|
193
|
+
| Some b when cs.cs_opname = None -> b.c_ctrl
|
|
194
|
+
| _ -> new_control ())
|
|
193
195
|
in
|
|
194
196
|
let meta =
|
|
195
197
|
match cs.cs_meta with Some (Map _ as m) -> m
|
|
@@ -413,7 +415,7 @@ let prepare_query_util (ctx : ctx) : value =
|
|
|
413
415
|
let out = empty_map () in
|
|
414
416
|
List.iter (fun k ->
|
|
415
417
|
let v = getp reqmatch k in
|
|
416
|
-
if not (is_noval v) && not (contains_param k) then setp out k v)
|
|
418
|
+
if not (is_noval v) && k <> "$action" && not (contains_param k) then setp out k v)
|
|
417
419
|
(keysof reqmatch);
|
|
418
420
|
out
|
|
419
421
|
|
|
@@ -555,14 +557,27 @@ let prepare_auth_util = Sdk_prepare_auth.prepare_auth_util
|
|
|
555
557
|
|
|
556
558
|
(* ----- transforms / result helpers ----- *)
|
|
557
559
|
|
|
560
|
+
(* `$action` selects the point (see make_point_util); it is never an API
|
|
561
|
+
field, so the body is a copy without it. The caller's map is left
|
|
562
|
+
untouched. *)
|
|
563
|
+
let strip_action (reqdata : value) : value =
|
|
564
|
+
match reqdata with
|
|
565
|
+
| Map _ when List.mem "$action" (keysof reqdata) ->
|
|
566
|
+
let body = empty_map () in
|
|
567
|
+
List.iter (fun k -> if k <> "$action" then setp body k (getp reqdata k))
|
|
568
|
+
(keysof reqdata);
|
|
569
|
+
body
|
|
570
|
+
| _ -> reqdata
|
|
571
|
+
|
|
558
572
|
let transform_request_util (ctx : ctx) : value =
|
|
559
573
|
(match ctx.c_spec with Some s -> s.sp_step <- "reqform" | None -> ());
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
574
|
+
strip_action
|
|
575
|
+
(match to_map (getp ctx.c_point "transform") with
|
|
576
|
+
| Map _ as tr ->
|
|
577
|
+
(match getp tr "req" with
|
|
578
|
+
| Noval -> ctx.c_reqdata
|
|
579
|
+
| reqform -> transform (jo [("reqdata", ctx.c_reqdata)]) reqform)
|
|
580
|
+
| _ -> ctx.c_reqdata)
|
|
566
581
|
|
|
567
582
|
let transform_response_util (ctx : ctx) : value =
|
|
568
583
|
(match ctx.c_spec with Some s -> s.sp_step <- "resform" | None -> ());
|
|
@@ -45,7 +45,8 @@ sub new {
|
|
|
45
45
|
$self->{ctrl}{actor} = $ctrl_raw->{actor} if exists $ctrl_raw->{actor};
|
|
46
46
|
$self->{ctrl}{paging} = $ctrl_raw->{paging} if Voxgig::Struct::ismap($ctrl_raw->{paging});
|
|
47
47
|
}
|
|
48
|
-
elsif ($basectx && $basectx->{ctrl}
|
|
48
|
+
elsif ($basectx && $basectx->{ctrl}
|
|
49
|
+
&& !defined ProjectNameHelpers::get_ctx_prop($ctxmap, 'opname')) {
|
|
49
50
|
$self->{ctrl} = $basectx->{ctrl};
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
# stamps page/limit (or a cursor) into the request query; on the way back
|
|
5
5
|
# (PreResult) it reads the server's pagination signals - a Link rel="next"
|
|
6
6
|
# header, X-Page/X-Next-Page/X-Total-Count headers, or next/cursor/
|
|
7
|
-
# nextCursor/hasMore fields in the body - and records
|
|
8
|
-
# result.paging. A per-call ctrl paging value (page or cursor) takes
|
|
7
|
+
# nextCursor/hasMore fields in the body (snake_case forms too) - and records
|
|
8
|
+
# them on result.paging and the caller's ctrl paging. A per-call ctrl paging value (page or cursor) takes
|
|
9
9
|
# priority. Parameter names ("pageParam", "limitParam", "cursorParam"),
|
|
10
10
|
# "startPage" (default 1) and page size ("limit") are configurable.
|
|
11
11
|
|
|
@@ -76,8 +76,11 @@ sub PreRequest {
|
|
|
76
76
|
$spec->{query}{$cursor_param} = $paging->{cursor};
|
|
77
77
|
}
|
|
78
78
|
elsif (!defined $spec->{query}{$page_param}) {
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
# A record written back by PreResult holds the page just fetched as
|
|
80
|
+
# "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
81
|
+
my $page = defined $paging->{nextPage} ? $paging->{nextPage} : $paging->{page};
|
|
82
|
+
$spec->{query}{$page_param} = defined $page
|
|
83
|
+
? $page
|
|
81
84
|
: (defined $self->{options}{startPage} ? $self->{options}{startPage} : 1);
|
|
82
85
|
}
|
|
83
86
|
|
|
@@ -186,15 +189,27 @@ sub PreResult {
|
|
|
186
189
|
}
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
# Body-level
|
|
192
|
+
# Body-level signals; snake_case forms are read first so camelCase wins.
|
|
190
193
|
if (Voxgig::Struct::ismap($body)) {
|
|
191
194
|
my $bnext = ProjectNameHelpers::gp($body, 'next');
|
|
192
195
|
$paging->{next} = defined $paging->{next} ? $paging->{next} : $bnext
|
|
193
196
|
if defined $bnext;
|
|
197
|
+
my $bsnakecursor = ProjectNameHelpers::gp($body, 'next_cursor');
|
|
198
|
+
$paging->{cursor} = $bsnakecursor if defined $bsnakecursor;
|
|
194
199
|
my $bcursor = ProjectNameHelpers::gp($body, 'cursor');
|
|
195
200
|
$paging->{cursor} = $bcursor if defined $bcursor;
|
|
196
201
|
my $bnextcursor = ProjectNameHelpers::gp($body, 'nextCursor');
|
|
197
202
|
$paging->{cursor} = $bnextcursor if defined $bnextcursor;
|
|
203
|
+
unless (defined $paging->{nextPage}) {
|
|
204
|
+
my $np = ProjectNameHelpers::gp($body, 'nextPage');
|
|
205
|
+
$np = ProjectNameHelpers::gp($body, 'next_page') unless defined $np;
|
|
206
|
+
$paging->{nextPage} = $np if defined $np && !ref($np);
|
|
207
|
+
}
|
|
208
|
+
my $bsnakemore = $body->{has_more};
|
|
209
|
+
if (ProjectNameHelpers::is_true($bsnakemore) || ProjectNameHelpers::is_false($bsnakemore)) {
|
|
210
|
+
$paging->{hasMore} = ProjectNameHelpers::is_true($bsnakemore) ? 1 : 0;
|
|
211
|
+
$explicit_more = 1;
|
|
212
|
+
}
|
|
198
213
|
my $bhasmore = $body->{hasMore};
|
|
199
214
|
if (ProjectNameHelpers::is_true($bhasmore) || ProjectNameHelpers::is_false($bhasmore)) {
|
|
200
215
|
$paging->{hasMore} = ProjectNameHelpers::is_true($bhasmore) ? 1 : 0;
|
|
@@ -215,6 +230,17 @@ sub PreResult {
|
|
|
215
230
|
|
|
216
231
|
$result->{paging} = $paging;
|
|
217
232
|
|
|
233
|
+
if ($ctx->{ctrl}) {
|
|
234
|
+
# The Context shares the caller's paging hash by reference, so refill it
|
|
235
|
+
# in place; a fresh hash on ctrl never reaches the caller.
|
|
236
|
+
if (Voxgig::Struct::ismap($ctx->{ctrl}{paging})) {
|
|
237
|
+
%{ $ctx->{ctrl}{paging} } = %$paging;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
$ctx->{ctrl}{paging} = $paging;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
218
244
|
$self->{client}{_paging} = { 'last' => $paging };
|
|
219
245
|
return;
|
|
220
246
|
}
|
|
@@ -154,18 +154,11 @@ sub init {
|
|
|
154
154
|
my $args = $test_self->build_args($fctx, $op, $update_match);
|
|
155
155
|
my $found = Voxgig::Struct::select($entmap, $args);
|
|
156
156
|
my $ent = ProjectNameHelpers::ge($found, 0);
|
|
157
|
-
|
|
158
|
-
for my $k (sort keys %$entmap) {
|
|
159
|
-
if (Voxgig::Struct::ismap($entmap->{$k})) {
|
|
160
|
-
$ent = $entmap->{$k};
|
|
161
|
-
last;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
157
|
+
# update miss: 404, never another record
|
|
165
158
|
return $respond->($fctx, 404, undef, { 'statusText' => 'Not found' })
|
|
166
159
|
unless ProjectNameHelpers::rb_truthy($ent);
|
|
167
|
-
if (Voxgig::Struct::ismap($ent) && $fctx->{reqdata}) {
|
|
168
|
-
$ent
|
|
160
|
+
if (Voxgig::Struct::ismap($ent) && Voxgig::Struct::ismap($fctx->{reqdata})) {
|
|
161
|
+
Voxgig::Struct::merge([$ent, $fctx->{reqdata}]);
|
|
169
162
|
}
|
|
170
163
|
Voxgig::Struct::delprop($ent, '$KEY');
|
|
171
164
|
my $out = Voxgig::Struct::clone($ent);
|
|
@@ -290,18 +283,55 @@ sub make_netsim {
|
|
|
290
283
|
};
|
|
291
284
|
}
|
|
292
285
|
|
|
286
|
+
sub _point_terminal {
|
|
287
|
+
my ($p) = @_;
|
|
288
|
+
my $parts = ProjectNameHelpers::gp($p, 'parts');
|
|
289
|
+
return 0 unless Voxgig::Struct::islist($parts) && @$parts;
|
|
290
|
+
my $last = $parts->[-1];
|
|
291
|
+
return (defined $last && !ref $last && 0 == index($last, '{')) ? 1 : 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
sub _point_depth {
|
|
295
|
+
my ($p) = @_;
|
|
296
|
+
my $parts = ProjectNameHelpers::gp($p, 'parts');
|
|
297
|
+
return Voxgig::Struct::islist($parts) ? scalar @$parts : 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# The entity's own endpoint: a terminal `{param}` marks a record route, and
|
|
301
|
+
# among equals the shallower path wins (the same rule as make_point).
|
|
302
|
+
sub _pick_point {
|
|
303
|
+
my ($points) = @_;
|
|
304
|
+
return undef unless Voxgig::Struct::islist($points) && @$points;
|
|
305
|
+
my $point = $points->[0];
|
|
306
|
+
for my $cand (@$points[1 .. $#$points]) {
|
|
307
|
+
if (_point_terminal($cand) != _point_terminal($point)) {
|
|
308
|
+
$point = $cand if _point_terminal($cand);
|
|
309
|
+
}
|
|
310
|
+
elsif (_point_depth($cand) < _point_depth($point)) {
|
|
311
|
+
$point = $cand;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return $point;
|
|
315
|
+
}
|
|
316
|
+
|
|
293
317
|
sub build_args {
|
|
294
318
|
my ($self, $ctx, $op, $args) = @_;
|
|
295
319
|
my $opname = $op->{name};
|
|
296
320
|
my $entname = $ctx->{entity}->get_name;
|
|
297
321
|
my $points = ProjectNameHelpers::gpath($ctx->{config},
|
|
298
322
|
"entity.$entname.op.$opname.points");
|
|
299
|
-
my $point =
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
my $
|
|
323
|
+
my $point = _pick_point($points);
|
|
324
|
+
|
|
325
|
+
# Path AND query: a path-only read misses a query-addressed record
|
|
326
|
+
# (e.g. GET /result?trace_id=), which has no path param at all.
|
|
327
|
+
my $reqd = [];
|
|
328
|
+
for my $kind ('params', 'query') {
|
|
329
|
+
my $args_path = ProjectNameHelpers::gpath($point, "args.$kind");
|
|
330
|
+
my $reqd_args = Voxgig::Struct::select($args_path,
|
|
331
|
+
{ 'reqd' => Voxgig::Struct::JTRUE() });
|
|
332
|
+
my $names = Voxgig::Struct::transform($reqd_args, ['`$EACH`', '', '`$KEY.name`']);
|
|
333
|
+
push @$reqd, @$names if Voxgig::Struct::islist($names);
|
|
334
|
+
}
|
|
305
335
|
|
|
306
336
|
my $qand = [];
|
|
307
337
|
my $q = { '`$AND`' => $qand };
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
# Per-request timeout. Wraps the active transport with a deadline of "ms"
|
|
4
4
|
# milliseconds (default 30000; <= 0 disables). The transport is synchronous
|
|
5
5
|
# (HTTP::Tiny), so a hanging request is interrupted with a SIGALRM timer
|
|
6
|
-
# (Time::HiRes::ualarm)
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# (Time::HiRes::ualarm) where the platform has one; where it has none
|
|
7
|
+
# (Windows) the deadline is checked once the call returns. When an
|
|
8
|
+
# injectable "now" clock is supplied the elapsed clock time is checked
|
|
9
|
+
# instead, so tests can assert the deadline deterministically. Expiry
|
|
10
|
+
# yields an error with code "timeout".
|
|
9
11
|
|
|
10
12
|
use strict;
|
|
11
13
|
use warnings;
|
|
@@ -75,24 +77,26 @@ sub with_timeout {
|
|
|
75
77
|
# Live path: interrupt a hanging synchronous transport.
|
|
76
78
|
my ($res, $err);
|
|
77
79
|
my $timed_out = 0;
|
|
78
|
-
my $
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
1;
|
|
85
|
-
};
|
|
80
|
+
my $start = Time::HiRes::time();
|
|
81
|
+
local $SIG{ALRM} = sub { $timed_out = 1; die "ProjectNameTimeout\n" };
|
|
82
|
+
my $armed = eval { Time::HiRes::ualarm(int($ms * 1000)); 1 };
|
|
83
|
+
my $ok = eval {
|
|
84
|
+
($res, $err) = $inner->($ctx, $url, $fetchdef);
|
|
85
|
+
1;
|
|
86
86
|
};
|
|
87
|
-
|
|
87
|
+
my $e = $@;
|
|
88
|
+
eval { Time::HiRes::ualarm(0) } if $armed;
|
|
88
89
|
if (!$ok) {
|
|
89
|
-
my $e = $@;
|
|
90
90
|
if ($timed_out) {
|
|
91
91
|
$self->_track($ctx, $ms);
|
|
92
92
|
return (undef, $ctx->make_error('timeout', "Request exceeded timeout of ${ms}ms"));
|
|
93
93
|
}
|
|
94
94
|
die $e;
|
|
95
95
|
}
|
|
96
|
+
if (!$armed && (Time::HiRes::time() - $start) * 1000 > $ms) {
|
|
97
|
+
$self->_track($ctx, $ms);
|
|
98
|
+
return (undef, $ctx->make_error('timeout', "Request exceeded timeout of ${ms}ms"));
|
|
99
|
+
}
|
|
96
100
|
return ($res, $err);
|
|
97
101
|
}
|
|
98
102
|
|
|
@@ -267,6 +267,15 @@ sub subset {
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
|
|
270
|
+
# Whether the SDK built the feature at all. The activity record is the
|
|
271
|
+
# wrong probe: most features create theirs on first use, so an idle client
|
|
272
|
+
# has none and every section but the eager ones read as inert.
|
|
273
|
+
sub present {
|
|
274
|
+
my ($client, $name) = @_;
|
|
275
|
+
return scalar grep { ref $_ && ($_->{name} // '') eq $name }
|
|
276
|
+
@{ $client->{features} || [] };
|
|
277
|
+
}
|
|
278
|
+
|
|
270
279
|
sub record {
|
|
271
280
|
my ($client, $name) = @_;
|
|
272
281
|
return $client->{"_$name"};
|
|
@@ -295,7 +304,11 @@ for my $name (sort keys %{ $CORPUS->{feature} || {} }) {
|
|
|
295
304
|
# Probed by ACTIVATING it: the feature defaults to inactive, so an idle
|
|
296
305
|
# client never builds it and its absence says nothing.
|
|
297
306
|
my $probe = build_client({ feature => [ { name => $name, active => 1 } ] });
|
|
298
|
-
|
|
307
|
+
unless (present($probe, $name)) {
|
|
308
|
+
# The one line every runner prints for an inert section.
|
|
309
|
+
diag("feature.$name: inert (this SDK does not generate the feature)");
|
|
310
|
+
next;
|
|
311
|
+
}
|
|
299
312
|
|
|
300
313
|
my %by_key = map { $_->{key} => $_ } @ops;
|
|
301
314
|
|
|
@@ -16,6 +16,16 @@ package ProjectNameUtilities;
|
|
|
16
16
|
|
|
17
17
|
our %REGISTRY;
|
|
18
18
|
|
|
19
|
+
# One HTTP::Tiny per (proxy, redirect) setting: a client keeps its
|
|
20
|
+
# connection alive between requests, so one per request reused nothing.
|
|
21
|
+
my %HTTP_CLIENTS;
|
|
22
|
+
|
|
23
|
+
sub _http_client {
|
|
24
|
+
my (%new_args) = @_;
|
|
25
|
+
my $key = join "\n", map { "$_=$new_args{$_}" } sort keys %new_args;
|
|
26
|
+
return $HTTP_CLIENTS{$key} //= HTTP::Tiny->new(%new_args);
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
our $DefaultHttpFetch = sub {
|
|
20
30
|
my ($fullurl, $fetchdef) = @_;
|
|
21
31
|
$fetchdef = {} unless defined $fetchdef;
|
|
@@ -47,8 +57,12 @@ our $DefaultHttpFetch = sub {
|
|
|
47
57
|
$new_args{max_redirect} = 0
|
|
48
58
|
if defined $fetchdef->{redirect} && !ref $fetchdef->{redirect}
|
|
49
59
|
&& 'manual' eq $fetchdef->{redirect};
|
|
60
|
+
# Both keys: HTTP::Tiny lets the environment override its generic proxy.
|
|
61
|
+
$new_args{http_proxy} = $new_args{https_proxy} = "$fetchdef->{proxy}"
|
|
62
|
+
if defined $fetchdef->{proxy} && !ref $fetchdef->{proxy}
|
|
63
|
+
&& length $fetchdef->{proxy};
|
|
50
64
|
|
|
51
|
-
my $res = eval {
|
|
65
|
+
my $res = eval { _http_client(%new_args)->request($method, $fullurl, $opts) };
|
|
52
66
|
if (!$res) {
|
|
53
67
|
my $e = defined $@ ? "$@" : 'request failed';
|
|
54
68
|
$e =~ s/\s+\z//;
|
|
@@ -30,6 +30,7 @@ $REGISTRY{prepare_query} = sub {
|
|
|
30
30
|
for my $item (@$items) {
|
|
31
31
|
my ($key, $val) = @$item;
|
|
32
32
|
next unless ProjectNameHelpers::rb_truthy($val) && defined $key && !ref $key;
|
|
33
|
+
next if '$action' eq $key;
|
|
33
34
|
next if grep { defined $_ && !ref $_ && $_ eq $key } @$params;
|
|
34
35
|
$out->{$key} = $val;
|
|
35
36
|
}
|
|
@@ -15,16 +15,26 @@ package ProjectNameUtilities;
|
|
|
15
15
|
|
|
16
16
|
our %REGISTRY;
|
|
17
17
|
|
|
18
|
+
# `$action` selects the point (see make_point); it is never an API field, so
|
|
19
|
+
# the body is a copy without it. The caller's hash is left untouched.
|
|
20
|
+
my $strip_action = sub {
|
|
21
|
+
my ($reqdata) = @_;
|
|
22
|
+
return $reqdata unless 'HASH' eq ref $reqdata && exists $reqdata->{'$action'};
|
|
23
|
+
my %body = %$reqdata;
|
|
24
|
+
delete $body{'$action'};
|
|
25
|
+
return \%body;
|
|
26
|
+
};
|
|
27
|
+
|
|
18
28
|
$REGISTRY{transform_request} = sub {
|
|
19
29
|
my ($ctx) = @_;
|
|
20
30
|
my $spec = $ctx->{spec};
|
|
21
31
|
my $point = $ctx->{point};
|
|
22
32
|
$spec->{step} = 'reqform' if $spec;
|
|
23
33
|
my $transform = ProjectNameHelpers::to_map(ProjectNameHelpers::gp($point, 'transform'));
|
|
24
|
-
return $ctx->{reqdata} unless $transform;
|
|
34
|
+
return $strip_action->($ctx->{reqdata}) unless $transform;
|
|
25
35
|
my $reqform = ProjectNameHelpers::gp($transform, 'req');
|
|
26
|
-
return $ctx->{reqdata} unless ProjectNameHelpers::rb_truthy($reqform);
|
|
27
|
-
return Voxgig::Struct::transform({ 'reqdata' => $ctx->{reqdata} }, $reqform);
|
|
36
|
+
return $strip_action->($ctx->{reqdata}) unless ProjectNameHelpers::rb_truthy($reqform);
|
|
37
|
+
return $strip_action->(Voxgig::Struct::transform({ 'reqdata' => $ctx->{reqdata} }, $reqform));
|
|
28
38
|
};
|
|
29
39
|
|
|
30
40
|
1;
|
|
@@ -55,7 +55,11 @@ class ProjectNameContext
|
|
|
55
55
|
if (array_key_exists('actor', $ctrl_raw)) {
|
|
56
56
|
$this->ctrl->actor = $ctrl_raw['actor'];
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
if (isset($ctrl_raw['paging']) && is_array($ctrl_raw['paging'])) {
|
|
59
|
+
$this->ctrl->paging = $ctrl_raw['paging'];
|
|
60
|
+
}
|
|
61
|
+
} elseif ($basectx !== null && $basectx->ctrl !== null
|
|
62
|
+
&& ProjectNameHelpers::get_ctx_prop($ctxmap, "opname") === null) {
|
|
59
63
|
$this->ctrl = $basectx->ctrl;
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -21,11 +21,16 @@ class ProjectNameControl
|
|
|
21
21
|
// passed. The shared feature corpus caught it.
|
|
22
22
|
public mixed $actor;
|
|
23
23
|
|
|
24
|
+
// Per-call paging record: read by the paging feature before the request
|
|
25
|
+
// and written back after it. Declared for the same reason as `$actor`.
|
|
26
|
+
public mixed $paging;
|
|
27
|
+
|
|
24
28
|
public function __construct(array $opts = [])
|
|
25
29
|
{
|
|
26
30
|
$this->throw_err = $opts['throw_err'] ?? null;
|
|
27
31
|
$this->err = null;
|
|
28
32
|
$this->explain = $opts['explain'] ?? null;
|
|
29
33
|
$this->actor = $opts['actor'] ?? null;
|
|
34
|
+
$this->paging = $opts['paging'] ?? null;
|
|
30
35
|
}
|
|
31
36
|
}
|
|
@@ -9,8 +9,8 @@ require_once __DIR__ . '/BaseFeature.php';
|
|
|
9
9
|
// stamps page/limit (or a cursor) into the request query; on the way back
|
|
10
10
|
// (PreResult) it reads the server's pagination signals — a `Link:
|
|
11
11
|
// rel="next"` header, `X-Page`/`X-Next-Page`/`X-Total-Count` headers, or
|
|
12
|
-
// `next`/`cursor`/`nextCursor`/`hasMore` fields in the body
|
|
13
|
-
// them on `$ctx->result->paging`. A per-call `ctrl->paging` (page or
|
|
12
|
+
// `next`/`cursor`/`nextCursor`/`hasMore` fields in the body (snake_case
|
|
13
|
+
// forms too) — and records them on `$ctx->result->paging` and `ctrl->paging`. A per-call `ctrl->paging` (page or
|
|
14
14
|
// cursor) takes priority over the stamped defaults; parameter names and
|
|
15
15
|
// page size are configurable; `startPage` defaults to 1. Mirrors
|
|
16
16
|
// ts/src/feature/paging/PagingFeature.ts.
|
|
@@ -71,8 +71,10 @@ class ProjectNamePagingFeature extends ProjectNameBaseFeature
|
|
|
71
71
|
} elseif (($spec->query[$page_param] ?? null) === null) {
|
|
72
72
|
$start_page = is_numeric($this->options['startPage'] ?? null)
|
|
73
73
|
? $this->options['startPage'] : 1;
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
// A record written back by PreResult holds the page just fetched
|
|
75
|
+
// as "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
76
|
+
$page = $paging['nextPage'] ?? $paging['page'] ?? null;
|
|
77
|
+
$spec->query[$page_param] = $page !== null ? $page : $start_page;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
if (($this->options['limit'] ?? null) !== null
|
|
@@ -194,17 +196,30 @@ class ProjectNamePagingFeature extends ProjectNameBaseFeature
|
|
|
194
196
|
}
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
// Body-level
|
|
199
|
+
// Body-level signals; snake_case forms are read first so camelCase wins.
|
|
198
200
|
if (is_array($body)) {
|
|
199
201
|
if (($body['next'] ?? null) !== null) {
|
|
200
202
|
$paging['next'] = $paging['next'] ?? $body['next'];
|
|
201
203
|
}
|
|
204
|
+
if (($body['next_cursor'] ?? null) !== null) {
|
|
205
|
+
$paging['cursor'] = $body['next_cursor'];
|
|
206
|
+
}
|
|
202
207
|
if (($body['cursor'] ?? null) !== null) {
|
|
203
208
|
$paging['cursor'] = $body['cursor'];
|
|
204
209
|
}
|
|
205
210
|
if (($body['nextCursor'] ?? null) !== null) {
|
|
206
211
|
$paging['cursor'] = $body['nextCursor'];
|
|
207
212
|
}
|
|
213
|
+
if ($paging['nextPage'] === null) {
|
|
214
|
+
$np = $body['nextPage'] ?? $body['next_page'] ?? null;
|
|
215
|
+
if (is_int($np) || is_float($np) || is_string($np)) {
|
|
216
|
+
$paging['nextPage'] = $np;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (is_bool($body['has_more'] ?? null)) {
|
|
220
|
+
$paging['hasMore'] = $body['has_more'];
|
|
221
|
+
$explicit_more = true;
|
|
222
|
+
}
|
|
208
223
|
if (is_bool($body['hasMore'] ?? null)) {
|
|
209
224
|
$paging['hasMore'] = $body['hasMore'];
|
|
210
225
|
$explicit_more = true;
|
|
@@ -225,6 +240,12 @@ class ProjectNamePagingFeature extends ProjectNameBaseFeature
|
|
|
225
240
|
|
|
226
241
|
$result->paging = $paging;
|
|
227
242
|
|
|
243
|
+
// PHP arrays are values, so the record lands on this call's Control
|
|
244
|
+
// rather than on an array the caller passed.
|
|
245
|
+
if ($ctx->ctrl !== null) {
|
|
246
|
+
$ctx->ctrl->paging = $paging;
|
|
247
|
+
}
|
|
248
|
+
|
|
228
249
|
$this->client->_paging = ['last' => $paging];
|
|
229
250
|
}
|
|
230
251
|
|