Unverified Commit 7d65b9a9 authored by Hong Minhee's avatar Hong Minhee
Browse files

Merge pull request #446 from sij411/fix/426

Add TypeScript support for RFC 6570 URI Template expressions (e.g., {+identifier})
parents cbc8aca7 23de239d
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -50,6 +50,25 @@ To be released.
    interoperability with ActivityPub servers that emit relative URLs in
    properties like `icon.url` and `image.url`.  [[#411], [#443] by Jiwon Kwon]

 -  Added TypeScript support for all [RFC 6570] URI Template expression types
    in dispatcher path parameters.  Previously, only simple string expansion
    (`{identifier}`) was supported in TypeScript types, while the runtime
    already supported all RFC 6570 expressions.  Now TypeScript accepts all
    expression types including `{+identifier}` (reserved string expansion,
    recommended for URI identifiers), `{#identifier}` (fragment expansion),
    `{.identifier}` (label expansion), `{/identifier}` (path segments),
    `{;identifier}` (path-style parameters), `{?identifier}` (query component),
    and `{&identifier}` (query continuation).  [[#426], [#446] by Jiwon Kwon]

     -  Added `Rfc6570Expression<TParam>` type helper.
     -  Updated all dispatcher path type parameters to accept RFC 6570
        expressions: `setActorDispatcher()`, `setObjectDispatcher()`,
        `setInboxDispatcher()`, `setOutboxDispatcher()`,
        `setFollowingDispatcher()`, `setFollowersDispatcher()`,
        `setLikedDispatcher()`, `setFeaturedDispatcher()`,
        `setFeaturedTagsDispatcher()`, `setInboxListeners()`,
        `setCollectionDispatcher()`, and `setOrderedCollectionDispatcher()`.

 -  Added inverse properties for collections to Vocabulary API.
    [[FEP-5711], [#373], [#381] by Jiwon Kwon]

@@ -108,6 +127,7 @@ To be released.

[FEP-fe34]: https://w3id.org/fep/fe34
[FEP-5711]: https://w3id.org/fep/5711
[RFC 6570]: https://tools.ietf.org/html/rfc6570
[OStatus 1.0 Draft 2]: https://www.w3.org/community/ostatus/wiki/images/9/93/OStatus_1.0_Draft_2.pdf
[RFC 7033 Section 4.4.4.3]: https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.3
[#119]: https://github.com/fedify-dev/fedify/issues/119
@@ -120,11 +140,13 @@ To be released.
[#404]: https://github.com/fedify-dev/fedify/pull/404
[#407]: https://github.com/fedify-dev/fedify/pull/407
[#411]: https://github.com/fedify-dev/fedify/issues/411
[#426]: https://github.com/fedify-dev/fedify/issues/426
[#429]: https://github.com/fedify-dev/fedify/issues/429
[#431]: https://github.com/fedify-dev/fedify/pull/431
[#440]: https://github.com/fedify-dev/fedify/issues/440
[#441]: https://github.com/fedify-dev/fedify/issues/441
[#443]: https://github.com/fedify-dev/fedify/pull/443
[#446]: https://github.com/fedify-dev/fedify/pull/446

### @fedify/cli

+203 −43
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ import type {
  IdempotencyStrategy,
  InboxListenerSetters,
  ObjectCallbackSetters,
  ParamsKeyPath,
  Rfc6570Expression,
} from "./federation.ts";
import type {
  CollectionCallbacks,
@@ -115,7 +115,7 @@ export class FederationBuilderImpl<TContextData>
    string | symbol,
    CustomCollectionCallbacks<
      Object,
      Record<string, string>,
      string,
      RequestContext<TContextData>,
      TContextData
    >
@@ -530,20 +530,23 @@ export class FederationBuilderImpl<TContextData>
  setObjectDispatcher<TObject extends Object, TParam extends string>(
    // deno-lint-ignore no-explicit-any
    cls: (new (...args: any[]) => TObject) & { typeId: URL },
    path:
      `${string}{${TParam}}${string}{${TParam}}${string}{${TParam}}${string}`,
    path: `${string}${Rfc6570Expression<TParam>}${string}${Rfc6570Expression<
      TParam
    >}${string}${Rfc6570Expression<TParam>}${string}`,
    dispatcher: ObjectDispatcher<TContextData, TObject, TParam>,
  ): ObjectCallbackSetters<TContextData, TObject, TParam>;
  setObjectDispatcher<TObject extends Object, TParam extends string>(
    // deno-lint-ignore no-explicit-any
    cls: (new (...args: any[]) => TObject) & { typeId: URL },
    path: `${string}{${TParam}}${string}{${TParam}}${string}`,
    path: `${string}${Rfc6570Expression<TParam>}${string}${Rfc6570Expression<
      TParam
    >}${string}`,
    dispatcher: ObjectDispatcher<TContextData, TObject, TParam>,
  ): ObjectCallbackSetters<TContextData, TObject, TParam>;
  setObjectDispatcher<TObject extends Object, TParam extends string>(
    // deno-lint-ignore no-explicit-any
    cls: (new (...args: any[]) => TObject) & { typeId: URL },
    path: `${string}{${TParam}}${string}`,
    path: `${string}${Rfc6570Expression<TParam>}${string}`,
    dispatcher: ObjectDispatcher<TContextData, TObject, TParam>,
  ): ObjectCallbackSetters<TContextData, TObject, TParam>;
  setObjectDispatcher<TObject extends Object, TParam extends string>(
@@ -1220,73 +1223,230 @@ export class FederationBuilderImpl<TContextData>

  setCollectionDispatcher<
    TObject extends Object,
    TParams extends Record<string, string>,
    TParam extends string,
  >(
    name: string | symbol,
    // deno-lint-ignore no-explicit-any
    itemType: (new (...args: any[]) => TObject) & { typeId: URL },
    path: `${string}${Rfc6570Expression<
      TParam
    >}${string}${Rfc6570Expression<
      TParam
    >}${string}${Rfc6570Expression<
      TParam
    >}${string}${Rfc6570Expression<
      TParam
    >}${string}`,
    dispatcher: CustomCollectionDispatcher<
      TObject,
      TParam,
      RequestContext<TContextData>,
      TContextData
    >,
  ): CustomCollectionCallbackSetters<
    TParam,
    RequestContext<TContextData>,
    TContextData
  >;
  setCollectionDispatcher<
    TObject extends Object,
    TParam extends string,
  >(
    name: string | symbol,
    // deno-lint-ignore no-explicit-any
    itemType: (new (...args: any[]) => TObject) & { typeId: URL },
    path: `${string}${Rfc6570Expression<
      TParam
    >}${string}${Rfc6570Expression<
      TParam
    >}${string}${Rfc6570Expression<
      TParam
    >}${string}`,
    dispatcher: CustomCollectionDispatcher<
      TObject,
      TParam,
      RequestContext<TContextData>,
      TContextData
    >,
  ): CustomCollectionCallbackSetters<
    TParam,
    RequestContext<TContextData>,
    TContextData
  >;
  setCollectionDispatcher<
    TObject extends Object,
    TParam extends string,
  >(
    name: string | symbol,
    // deno-lint-ignore no-explicit-any
    itemType: (new (...args: any[]) => TObject) & { typeId: URL },
    path: `${string}${Rfc6570Expression<TParam>}${string}${Rfc6570Expression<
      TParam
    >}${string}`,
    dispatcher: CustomCollectionDispatcher<
      TObject,
      TParam,
      RequestContext<TContextData>,
      TContextData
    >,
  ): CustomCollectionCallbackSetters<
    TParam,
    RequestContext<TContextData>,
    TContextData
  >;
  setCollectionDispatcher<
    TObject extends Object,
    TParam extends string,
  >(
    name: string | symbol,
    // deno-lint-ignore no-explicit-any
    itemType: (new (...args: any[]) => TObject) & { typeId: URL },
    path: `${string}${Rfc6570Expression<TParam>}${string}`,
    dispatcher: CustomCollectionDispatcher<
      TObject,
      TParam,
      RequestContext<TContextData>,
      TContextData
    >,
  ): CustomCollectionCallbackSetters<
    TParam,
    RequestContext<TContextData>,
    TContextData
  >;
  setCollectionDispatcher<
    TObject extends Object,
    TParam extends string,
  >(
    name: string | symbol,
    ...args: [
      ConstructorWithTypeId<TObject>,
      ParamsKeyPath<TParams>,
      CustomCollectionDispatcher<
    // deno-lint-ignore no-explicit-any
    itemType: (new (...args: any[]) => TObject) & { typeId: URL },
    path: string,
    dispatcher: CustomCollectionDispatcher<
      TObject,
        TParams,
      TParam,
      RequestContext<TContextData>,
      TContextData
    >,
    ]
  ): CustomCollectionCallbackSetters<
    TParams,
    TParam,
    RequestContext<TContextData>,
    TContextData
  > {
    return this.#setCustomCollectionDispatcher(
      name,
      "collection",
      ...args,
      itemType,
      path as `${string}${Rfc6570Expression<TParam>}${string}`,
      dispatcher,
    );
  }

  setOrderedCollectionDispatcher<
    TObject extends Object,
    TParams extends Record<string, string>,
    TParam extends string,
  >(
    name: string | symbol,
    // deno-lint-ignore no-explicit-any
    itemType: (new (...args: any[]) => TObject) & { typeId: URL },
    path: `${string}${Rfc6570Expression<TParam>}${string}${Rfc6570Expression<
      TParam
    >}${string}${Rfc6570Expression<TParam>}${string}`,
    dispatcher: CustomCollectionDispatcher<
      TObject,
      TParam,
      RequestContext<TContextData>,
      TContextData
    >,
  ): CustomCollectionCallbackSetters<
    TParam,
    RequestContext<TContextData>,
    TContextData
  >;
  setOrderedCollectionDispatcher<
    TObject extends Object,
    TParam extends string,
  >(
    name: string | symbol,
    // deno-lint-ignore no-explicit-any
    itemType: (new (...args: any[]) => TObject) & { typeId: URL },
    path: `${string}${Rfc6570Expression<TParam>}${string}${Rfc6570Expression<
      TParam
    >}${string}`,
    dispatcher: CustomCollectionDispatcher<
      TObject,
      TParam,
      RequestContext<TContextData>,
      TContextData
    >,
  ): CustomCollectionCallbackSetters<
    TParam,
    RequestContext<TContextData>,
    TContextData
  >;
  setOrderedCollectionDispatcher<
    TObject extends Object,
    TParam extends string,
  >(
    name: string | symbol,
    ...args: [
      ConstructorWithTypeId<TObject>,
      ParamsKeyPath<TParams>,
      CustomCollectionDispatcher<
    // deno-lint-ignore no-explicit-any
    itemType: (new (...args: any[]) => TObject) & { typeId: URL },
    path: `${string}${Rfc6570Expression<TParam>}${string}`,
    dispatcher: CustomCollectionDispatcher<
      TObject,
        TParams,
      TParam,
      RequestContext<TContextData>,
      TContextData
    >,
    ]
  ): CustomCollectionCallbackSetters<
    TParams,
    TParam,
    RequestContext<TContextData>,
    TContextData
  >;
  setOrderedCollectionDispatcher<
    TObject extends Object,
    TParam extends string,
  >(
    name: string | symbol,
    // deno-lint-ignore no-explicit-any
    itemType: (new (...args: any[]) => TObject) & { typeId: URL },
    path: string,
    dispatcher: CustomCollectionDispatcher<
      TObject,
      TParam,
      RequestContext<TContextData>,
      TContextData
    >,
  ): CustomCollectionCallbackSetters<
    TParam,
    RequestContext<TContextData>,
    TContextData
  > {
    return this.#setCustomCollectionDispatcher(
      name,
      "orderedCollection",
      ...args,
      itemType,
      path as `${string}${Rfc6570Expression<TParam>}${string}`,
      dispatcher,
    );
  }

  #setCustomCollectionDispatcher<
    TObject extends Object,
    TParams extends Record<string, string>,
    TParam extends string,
  >(
    name: string | symbol,
    collectionType: "collection" | "orderedCollection",
    itemType: ConstructorWithTypeId<TObject>,
    path: ParamsKeyPath<TParams>,
    // deno-lint-ignore no-explicit-any
    itemType: (new (...args: any[]) => TObject) & { typeId: URL },
    path: `${string}${Rfc6570Expression<TParam>}${string}`,
    dispatcher: CustomCollectionDispatcher<
      TObject,
      TParams,
      TParam,
      RequestContext<TContextData>,
      TContextData
    >,
  ): CustomCollectionCallbackSetters<
    TParams,
    TParam,
    RequestContext<TContextData>,
    TContextData
  > {
@@ -1314,7 +1474,7 @@ export class FederationBuilderImpl<TContextData>

    const callbacks: CustomCollectionCallbacks<
      TObject,
      TParams,
      TParam,
      RequestContext<TContextData>,
      TContextData
    > = { dispatcher };
@@ -1324,13 +1484,13 @@ export class FederationBuilderImpl<TContextData>
    this.collectionTypeIds[name] = itemType;

    const setters: CustomCollectionCallbackSetters<
      TParams,
      TParam,
      RequestContext<TContextData>,
      TContextData
    > = {
      setCounter(
        counter: CustomCollectionCounter<
          TParams,
          TParam,
          TContextData
        >,
      ) {
@@ -1339,7 +1499,7 @@ export class FederationBuilderImpl<TContextData>
      },
      setFirstCursor(
        cursor: CustomCollectionCursor<
          TParams,
          TParam,
          RequestContext<TContextData>,
          TContextData
        >,
@@ -1349,7 +1509,7 @@ export class FederationBuilderImpl<TContextData>
      },
      setLastCursor(
        cursor: CustomCollectionCursor<
          TParams,
          TParam,
          RequestContext<TContextData>,
          TContextData
        >,
@@ -1360,7 +1520,7 @@ export class FederationBuilderImpl<TContextData>
      authorize(
        predicate: ObjectAuthorizePredicate<
          TContextData,
          keyof TParams & string
          TParam
        >,
      ) {
        callbacks.authorizePredicate = predicate;
+6 −6
Original line number Diff line number Diff line
@@ -304,12 +304,12 @@ export type ObjectAuthorizePredicate<TContextData, TParam extends string> = (
 */
export type CustomCollectionDispatcher<
  TItem,
  TParams extends Record<string, string>,
  TParam extends string,
  TContext extends Context<TContextData>,
  TContextData,
> = (
  context: TContext,
  values: TParams,
  values: Record<TParam, string>,
  cursor: string | null,
) => PageItems<TItem> | null | Promise<PageItems<TItem> | null>;

@@ -323,11 +323,11 @@ export type CustomCollectionDispatcher<
 * @since 1.8.0
 */
export type CustomCollectionCounter<
  TParams extends Record<string, string>,
  TParam extends string,
  TContextData,
> = (
  context: RequestContext<TContextData>,
  values: TParams,
  values: Record<TParam, string>,
) => number | bigint | null | Promise<number | bigint | null>;

/**
@@ -343,10 +343,10 @@ export type CustomCollectionCounter<
 * @since 1.8.0
 */
export type CustomCollectionCursor<
  TParams extends Record<string, string>,
  TParam extends string,
  TContext extends Context<TContextData>,
  TContextData,
> = (
  context: TContext,
  values: TParams,
  values: Record<TParam, string>,
) => string | null | Promise<string | null>;
+228 −102

File changed.

Preview size limit exceeded, changes collapsed.

+5 −5
Original line number Diff line number Diff line
@@ -1543,7 +1543,7 @@ test("handleCustomCollection()", async () => {
  // Mock dispatcher similar to collection dispatcher pattern
  const dispatcher: CustomCollectionDispatcher<
    Create,
    Record<string, string>,
    string,
    RequestContext<void>,
    void
  > = (
@@ -1568,13 +1568,13 @@ test("handleCustomCollection()", async () => {
    return { items };
  };

  const counter: CustomCollectionCounter<Record<string, string>, void> = (
  const counter: CustomCollectionCounter<string, void> = (
    _ctx: RequestContext<void>,
    values: Record<string, string>,
  ) => values.handle === "someone" ? 3 : null;

  const firstCursor: CustomCollectionCursor<
    Record<string, string>,
    string,
    RequestContext<void>,
    void
  > = (
@@ -1583,7 +1583,7 @@ test("handleCustomCollection()", async () => {
  ) => values.handle === "someone" ? "0" : null;

  const lastCursor: CustomCollectionCursor<
    Record<string, string>,
    string,
    RequestContext<void>,
    void
  > = (
@@ -1593,7 +1593,7 @@ test("handleCustomCollection()", async () => {

  const callbacks: CustomCollectionCallbacks<
    Create,
    Record<string, string>,
    string,
    RequestContext<void>,
    void
  > = {
Loading