From fe58a9323fc5e43e51137c483ddf3cd2979744f6 Mon Sep 17 00:00:00 2001 From: patrick-rodgers Date: Mon, 18 Oct 2021 16:43:44 -0400 Subject: [PATCH] added AssignFrom, updates --- .vscode/launch.json | 2 +- debug/launch/v3-patrick.ts | 5 +- docs/core/timeline.md | 129 ++++++++++- package-lock.json | 6 + package.json | 1 + packages/core/behaviors/assign-from.ts | 17 ++ .../behaviors/{copyfrom.ts => copy-from.ts} | 0 packages/core/index.ts | 3 +- packages/core/timeline.ts | 8 +- packages/graph/onedrive/types.ts | 6 +- packages/nodejs/sp-extensions/stream.ts | 9 +- ...gPessimistic.ts => caching-pessimistic.ts} | 6 +- .../queryable/behaviors/queryable-copyfrom.ts | 25 --- packages/queryable/behaviors/throw.ts | 19 ++ packages/queryable/index.ts | 4 +- packages/queryable/queryable.ts | 20 +- packages/sp-addinhelpers/sprestaddin.ts | 4 +- packages/sp/appcatalog/index.ts | 34 +-- packages/sp/appcatalog/types.ts | 11 +- packages/sp/clientside-pages/types.ts | 18 +- packages/sp/column-defaults/folder.ts | 6 +- packages/sp/column-defaults/list.ts | 4 +- packages/sp/comments/item.ts | 2 +- packages/sp/comments/types.ts | 2 +- packages/sp/files/types.ts | 8 +- packages/sp/files/web.ts | 2 +- packages/sp/folders/types.ts | 8 +- packages/sp/folders/web.ts | 2 +- packages/sp/index.ts | 16 +- packages/sp/items/types.ts | 8 +- packages/sp/lists/types.ts | 6 +- packages/sp/lists/web.ts | 4 +- packages/sp/presets/all.ts | 2 +- packages/sp/profiles/types.ts | 7 +- packages/sp/related-items/types.ts | 2 +- packages/sp/sharing/funcs.ts | 2 +- packages/sp/site-designs/types.ts | 2 +- packages/sp/site-scripts/list.ts | 2 +- packages/sp/site-scripts/types.ts | 4 +- packages/sp/site-users/web.ts | 2 +- packages/sp/sites/types.ts | 14 +- packages/sp/spqueryable.ts | 6 +- packages/sp/sputilities/types.ts | 2 +- ...peQueryStrValue.ts => escape-query-str.ts} | 0 .../{extractweburl.ts => extract-web-url.ts} | 0 .../{odataUrlFrom.ts => odata-url-from.ts} | 2 +- ...{toResourcePath.ts => to-resource-path.ts} | 0 packages/sp/webs/types.ts | 11 +- test/core/timeline.ts | 205 ++++++++++++++++++ test/queryable/add-prop.ts | 24 ++ test/queryable/behaviors.ts | 84 ++++++- test/queryable/invokable.ts | 35 +++ test/queryable/queryable.ts | 115 ++++++++++ 53 files changed, 756 insertions(+), 160 deletions(-) create mode 100644 packages/core/behaviors/assign-from.ts rename packages/core/behaviors/{copyfrom.ts => copy-from.ts} (100%) rename packages/queryable/behaviors/{cachingPessimistic.ts => caching-pessimistic.ts} (96%) delete mode 100644 packages/queryable/behaviors/queryable-copyfrom.ts create mode 100644 packages/queryable/behaviors/throw.ts rename packages/sp/utils/{escapeQueryStrValue.ts => escape-query-str.ts} (100%) rename packages/sp/utils/{extractweburl.ts => extract-web-url.ts} (100%) rename packages/sp/utils/{odataUrlFrom.ts => odata-url-from.ts} (96%) rename packages/sp/utils/{toResourcePath.ts => to-resource-path.ts} (100%) create mode 100644 test/core/timeline.ts create mode 100644 test/queryable/add-prop.ts create mode 100644 test/queryable/invokable.ts create mode 100644 test/queryable/queryable.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 7ca51146d..25a721c03 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -36,7 +36,7 @@ "runtimeArgs": [], "sourceMaps": true, "outFiles": [], - "args": ["--noretries", "--verbose", "--cleanup","--logging"], + "args": ["--noretries", "--verbose", "--cleanup","--logging", "--packages", "queryable", "--skip-web"], "console": "internalConsole", "internalConsoleOptions": "openOnSessionStart", "skipFiles": [ diff --git a/debug/launch/v3-patrick.ts b/debug/launch/v3-patrick.ts index a96510541..6395ae375 100644 --- a/debug/launch/v3-patrick.ts +++ b/debug/launch/v3-patrick.ts @@ -6,6 +6,7 @@ import "@pnp/sp/webs"; import "@pnp/sp/lists"; import "@pnp/sp/files"; import "@pnp/sp/folders"; +import "@pnp/sp/appcatalog"; declare var process: { exit(code?: number): void }; @@ -49,7 +50,9 @@ export async function Example(settings: ITestingSettings) { }, })).using(PnPLogging(LogLevel.Verbose)); - const y = await sp2.web(); + const web = await sp2.getTenantAppCatalogWeb(); + + const y = await web(); console.log(JSON.stringify(y)); diff --git a/docs/core/timeline.md b/docs/core/timeline.md index c9a9d8f15..b8ee7d208 100644 --- a/docs/core/timeline.md +++ b/docs/core/timeline.md @@ -19,11 +19,13 @@ The `first` moment uses a pre-defined moment implementation `asyncReduce`. This import { asyncReduce, ObserverAction, Timeline } from "@pnp/core"; // the first observer is a function taking a number and async returning a number in an array +// all asyncReduce observers must follow this patter of returning async a tuple matching the args export type FirstObserver = (this: any, counter: number) => Promise<[number]>; // the second observer is a function taking a number and returning void export type SecondObserver = (this: any, result: number) => void; +// this is a custom moment definition as an example. Please read more about moments on the moments page export function report(): (observers: T[], ...args: any[]) => void { return function (observers: T[], ...args: any[]): void { @@ -37,39 +39,146 @@ export function report(): (observers: T[], ...args: an }; } -const MyMoments = { +// this plain object defines the moments which will be available in our timeline +// the property name "first" and "second" will be the moment names, used when we make calls such as instance.on.first and instance.on.second +const TestingMoments = { first: asyncReduce(), second: report(), } as const; +// note as well the use of as const, this allows TypeScript to properly resolve all the complex typings and not treat the plain object as "any" ``` +### Subclass Timeline +After defining our moments we need to subclass Timeline to define how those moments emit through the lifecycle of the Timeline. Timeline has a single abstract method "execute" you must implement. You will also need to provide a way for callers to trigger the protected "start" method. +```TypeScript +// our implementation of timeline, note we use `typeof TestingMoments` and ALSO pass the testing moments object to super() in the constructor +class TestTimeline extends Timeline { + + // we create two unique refs for our implementation we will use + // to resolve the execute promise + private InternalResolveEvent = Symbol.for("Resolve"); + private InternalRejectEvent = Symbol.for("Reject"); + + constructor() { + // we need to pass the moments to the base Timeline + super(TestingMoments); + } + + // we implement the execute the method to define when, in what order, and how our moments are called. This give you full control within the Timeline framework + // to determine your implementation's behavior + protected async execute(init?: any): Promise { + + // we can always emit log to any subscribers + this.log("Starting", 0); + + // set our timeline to start in the next tick + setTimeout(async () => { + + try { + + // we emit our "first" event + let [value] = await this.emit.first(init); + + // we emit our "second" event + [value] = await this.emit.second(value); + + // we reolve the execute promise with the final value + this.emit[this.InternalResolveEvent](value); + + } catch (e) { + + // we emit our reject event + this.emit[this.InternalRejectEvent](e); + // we emit error to any subscribed observers + this.error(e); + } + }, 0); + + // return a promise which we will resolve/reject during the timeline lifecycle + return new Promise((resolve, reject) => { + this.on[this.InternalResolveEvent].replace(resolve); + this.on[this.InternalRejectEvent].replace(reject); + }); + } + + // provide a method to trigger our timeline, this could be protected or called directly by the user, your choice + public go(startValue = 0): Promise { + + // here we take a starting number + return this.start(startValue); + } +} +``` + +### Using your Timeline + +```TypeScript +import { TestTimeline } from "./file.js"; + +const tl = new TestTimeline(); +// register observer +tl.on.first(async (n) => [++n]); +// register observer +tl.on.second(async (n) => [++n]); +// h === 2 +const h = await tl.go(0); +// h === 7 +const h2 = await tl.go(5); +``` +## Understanding the Timline Lifecycle +Now that you implemented a simple timeline let's take a minute to understand the lifecycle of a timeline execution. There are four moments always defined for every timeline: init, dispose, log, and error. Of these init and dispose are used within the lifecycle, while log and error are used as you need. +### Timeline Lifecycle +- init +- your moments as defined in execute, in our example + - first + - second +- dispose -Timeline - intro, what is it conceptually +## Observer Inheritance -describe the parts -> timeline, moments definition, observers +Let's say that you want to contruct a system whereby you can create Timeline based instances from other Timeline based instances - which is what [Queryable](../queryable/queryable.md) does. Imagine we have a class with a pseudo-signature like: -show custom timeline -- moments def -- execute impl +```TypeScript +class ATimeline extends Timeline { -- show observer registration + // we create two unique refs for our implementation we will use + // to resolve the execute promise + private InternalResolveEvent = Symbol.for("Resolve"); + private InternalRejectEvent = Symbol.for("Reject"); -- show behavior registering multiple observers + constructor(base: ATimeline) { -- link to batching docs and code as an example of a complex behavior -- link to other behaviors to show what's what + // we need to pass the moments to the base Timeline + super(TestingMoments, base.observers); + } + //... +} +``` +We can then use it like: +```TypeScript +const tl1 = new ATimeline(); +tl1.on.first(async (n) => [++n]); +tl1.on.second(async (n) => [++n]); + +// at this point tl2's observer collection is a pointer/reference to the same collection as tl1 +const tl2 = new ATimeline(tl1); +// we add a second observer to first, it is applied to BOTH tl1 and tl2 +tl1.on.first(async (n) => [++n]); +// BUT when we modify tl2's observers, either by adding or clearing a moment it begins to track its own collection +tl2.on.first(async (n) => [++n]); +``` diff --git a/package-lock.json b/package-lock.json index 0d8d1cc4b..67c2db293 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5846,6 +5846,12 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "node-abort-controller": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.0.1.tgz", + "integrity": "sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw==", + "dev": true + }, "node-fetch": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", diff --git a/package.json b/package.json index 212e3c1c9..499277912 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "lodash.clonedeep": "^4.5.0", "mocha": "^9.1.2", "msal": "^1.4.6", + "node-abort-controller": "^3.0.1", "node-fetch": "^2.6.1", "prettyjson": "^1.2.1", "string-replace-loader": "^3.0.1", diff --git a/packages/core/behaviors/assign-from.ts b/packages/core/behaviors/assign-from.ts new file mode 100644 index 000000000..8b1ec5201 --- /dev/null +++ b/packages/core/behaviors/assign-from.ts @@ -0,0 +1,17 @@ +import { Timeline, TimelinePipe } from "@pnp/core"; + +/** + * Behavior that will assign a ref to the source's observers and reset the instance's inheriting flag + * + * @param source The source instance from which we will assign the observers + */ +export function AssignFrom(source: Timeline): TimelinePipe { + + return (instance: Timeline) => { + + (instance).observers = (source).observers; + (instance)._inheritingObservers = true; + + return instance; + }; +} diff --git a/packages/core/behaviors/copyfrom.ts b/packages/core/behaviors/copy-from.ts similarity index 100% rename from packages/core/behaviors/copyfrom.ts rename to packages/core/behaviors/copy-from.ts diff --git a/packages/core/index.ts b/packages/core/index.ts index 29fd7b536..879753563 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -8,4 +8,5 @@ export * from "./extendable.js"; /** * Behavior exports */ -export * from "./behaviors/copyfrom.js"; +export * from "./behaviors/assign-from.js"; +export * from "./behaviors/copy-from.js"; diff --git a/packages/core/timeline.ts b/packages/core/timeline.ts index e0dc34805..b107b2a26 100644 --- a/packages/core/timeline.ts +++ b/packages/core/timeline.ts @@ -50,7 +50,7 @@ type DistributeEmit = /** * Virtual events that are present on all Timelines */ -type DefaultTimelineEvents = { +type DefaultTimelineMoments = { init: (observers: ((this: Timeline) => void)[], ...args: any[]) => void; dispose: (observers: ((this: Timeline) => void)[], ...args: any[]) => void; log: (observers: ((this: Timeline, message: string, level: number) => void)[], ...args: any[]) => void; @@ -60,12 +60,12 @@ type DefaultTimelineEvents = { /** * The type combining the defined moments and DefaultTimelineEvents */ -type OnProxyType = DistributeOn & DistributeOn, T>; +type OnProxyType = DistributeOn & DistributeOn, T>; /** * The type combining the defined moments and DefaultTimelineEvents */ -type EmitProxyType = DistributeEmit & DistributeEmit>; +type EmitProxyType = DistributeEmit & DistributeEmit>; /** * Represents a function accepting and returning a timeline, possibly manipulating the observers present @@ -129,7 +129,7 @@ export abstract class Timeline { if (Reflect.has(target.observers, p)) { target.cloneObserversOnChange(); - // we trust outselves that this will be an array + // we trust ourselves that this will be an array (target.observers)[p].length = 0; return true; } diff --git a/packages/graph/onedrive/types.ts b/packages/graph/onedrive/types.ts index 3ff785c89..36c800a04 100644 --- a/packages/graph/onedrive/types.ts +++ b/packages/graph/onedrive/types.ts @@ -9,9 +9,9 @@ import { GraphQueryable, } from "../graphqueryable.js"; import { Drive as IDriveType } from "@microsoft/microsoft-graph-types"; -import { combine } from "@pnp/core"; +import { combine, AssignFrom } from "@pnp/core"; import { defaultPath, getById, IGetById, deleteable, IDeleteable, updateable, IUpdateable } from "../decorators.js"; -import { body, CopyFromQueryable, BlobParse } from "@pnp/queryable"; +import { body, BlobParse } from "@pnp/queryable"; import { graphPatch, graphPut } from "../operations.js"; /** @@ -108,7 +108,7 @@ export class _DriveItem extends _GraphQueryableInstance { // TODO:: make sure this works public async getContent(): Promise { const info = await this(); - const query = GraphQueryable(info["@microsoft.graph.downloadUrl"], null).using(BlobParse).using(CopyFromQueryable(this)); + const query = GraphQueryable(info["@microsoft.graph.downloadUrl"], null).using(BlobParse).using(AssignFrom(this)); query.on.pre(async (url, init, result) => { diff --git a/packages/nodejs/sp-extensions/stream.ts b/packages/nodejs/sp-extensions/stream.ts index ceab2bf9c..2bcf6e46f 100644 --- a/packages/nodejs/sp-extensions/stream.ts +++ b/packages/nodejs/sp-extensions/stream.ts @@ -1,12 +1,11 @@ import { getGUID, isFunc } from "@pnp/core/util"; -import { CopyFromQueryable, headers } from "@pnp/queryable"; +import { headers } from "@pnp/queryable"; import { File, Files, IFile, IFileAddResult, IFileInfo, IFiles, IFileUploadProgressData } from "@pnp/sp/files"; import { spPost } from "@pnp/sp/operations"; -import { escapeQueryStrValue } from "@pnp/sp/utils/escapeQueryStrValue"; import { ReadStream } from "fs"; import { PassThrough } from "stream"; -import { extendFactory } from "@pnp/core"; -import { odataUrlFrom } from "@pnp/sp"; +import { AssignFrom, extendFactory } from "@pnp/core"; +import { odataUrlFrom, escapeQueryStrValue } from "@pnp/sp"; import { StreamParse } from "../behaviors/stream-parse.js"; export interface IResponseBodyStream { @@ -87,7 +86,7 @@ extendFactory(Files, { ): Promise { const response: IFileInfo = await spPost(Files(this, `add(overwrite=${shouldOverWrite},url='${escapeQueryStrValue(url)}')`)); - const file = File(odataUrlFrom(response)).using(CopyFromQueryable(this)); + const file = File(odataUrlFrom(response)).using(AssignFrom(this)); if ("function" === typeof (content as ReadStream).read) { return file.setStreamContentChunked(content as ReadStream, progress); diff --git a/packages/queryable/behaviors/cachingPessimistic.ts b/packages/queryable/behaviors/caching-pessimistic.ts similarity index 96% rename from packages/queryable/behaviors/cachingPessimistic.ts rename to packages/queryable/behaviors/caching-pessimistic.ts index 759ef39d1..856b43d32 100644 --- a/packages/queryable/behaviors/cachingPessimistic.ts +++ b/packages/queryable/behaviors/caching-pessimistic.ts @@ -78,6 +78,7 @@ export function CachingPessimisticRefresh( const emitError = (e) => { this.emit.log(`[id:${requestId}] Emitting error: "${e.message || e}"`, 3); + this.emit[this.InternalRejectEvent](e); this.emit.error(e); this.emit.log(`[id:${requestId}] Emitted error: "${e.message || e}"`, 3); }; @@ -108,6 +109,7 @@ export function CachingPessimisticRefresh( const emitData = () => { this.emit.log(`[id:${requestId}] Emitting data`, 0); + this.emit[this.InternalResolveEvent](retVal); this.emit.data(retVal); this.emit.log(`[id:${requestId}] Emitted data`, 0); }; @@ -160,8 +162,8 @@ export function CachingPessimisticRefresh( }, 0); return new Promise((resolve, reject) => { - this.on.data(resolve); - this.on.error(reject); + this.on[this.InternalResolveEvent].replace(resolve); + this.on[this.InternalRejectEvent].replace(reject); }); }, }); diff --git a/packages/queryable/behaviors/queryable-copyfrom.ts b/packages/queryable/behaviors/queryable-copyfrom.ts deleted file mode 100644 index 4dafdf83e..000000000 --- a/packages/queryable/behaviors/queryable-copyfrom.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { CopyFrom, TimelinePipe } from "@pnp/core"; -import { Queryable } from "../queryable.js"; - -/** - * Behavior that will copy all the observers in the source queryable and apply it to the incoming instance - * - * @param source The source instance from which we will copy the observers - * @param behavior replace = observers are cleared before adding, append preserves any observers already present - * @param keepData If true any subscribed data members are removed (Default: false) - */ -export function CopyFromQueryable(source: Queryable, behavior: "replace" | "append" = "append", keepData = false): TimelinePipe { - - const coreFrom = CopyFrom(source, behavior); - - return (instance: Queryable) => { - - instance = coreFrom(instance); - - if (!keepData) { - instance.on.data.clear(); - } - - return instance; - }; -} diff --git a/packages/queryable/behaviors/throw.ts b/packages/queryable/behaviors/throw.ts new file mode 100644 index 000000000..6def29c47 --- /dev/null +++ b/packages/queryable/behaviors/throw.ts @@ -0,0 +1,19 @@ +import { TimelinePipe } from "../../core/timeline.js"; +import { Queryable } from "../queryable.js"; + +export function ThrowErrors(): TimelinePipe { + + return (instance: Queryable) => { + + instance.on.pre(async function (this: Queryable, url, init, result) { + + this.on.error((err) => { + throw err; + }); + + return [url, init, result]; + }); + + return instance; + }; +} diff --git a/packages/queryable/index.ts b/packages/queryable/index.ts index 9219610f6..854563663 100644 --- a/packages/queryable/index.ts +++ b/packages/queryable/index.ts @@ -11,8 +11,8 @@ export * from "./request-builders.js"; export * from "./behaviors/bearer-token.js"; export * from "./behaviors/browser-fetch.js"; export * from "./behaviors/caching.js"; -export * from "./behaviors/cachingPessimistic.js"; +export * from "./behaviors/caching-pessimistic.js"; export * from "./behaviors/inject-headers.js"; export * from "./behaviors/parsers.js"; -export * from "./behaviors/queryable-copyfrom.js"; export * from "./behaviors/timeout.js"; +export * from "./behaviors/throw.js"; diff --git a/packages/queryable/queryable.ts b/packages/queryable/queryable.ts index d9a3a8a6b..af21e7237 100644 --- a/packages/queryable/queryable.ts +++ b/packages/queryable/queryable.ts @@ -26,8 +26,10 @@ const DefaultMoments = { @invokable() export class Queryable extends Timeline implements IQueryableInternal { - protected _url: string; private _query: Map; + protected _url: string; + protected InternalResolveEvent = Symbol.for("Queryable_Resolve"); + protected InternalRejectEvent = Symbol.for("Queryable_Reject"); constructor(init: Queryable | string, path?: string) { @@ -43,9 +45,6 @@ export class Queryable extends Timeline implements IQu const { _url } = init; url = combine(_url, path); - // TODO:: doesn't work due to data event (maybe others) - // pre, post, send, auth, error, log - // data observers = init.observers; } @@ -115,6 +114,7 @@ export class Queryable extends Timeline implements IQu this.log(`[request:${requestId}] Result returned from pre`, 1); this.log(`[request:${requestId}] Emitting data`, 0); + this.emit[this.InternalResolveEvent](result); this.emit.data(result); this.log(`[request:${requestId}] Emitted data`, 0); @@ -142,14 +142,20 @@ export class Queryable extends Timeline implements IQu // completed event to signal the request is completed? if (typeof result !== "undefined") { this.log(`[request:${requestId}] Emitting data`, 0); + this.emit[this.InternalResolveEvent](result); this.emit.data(result); this.log(`[request:${requestId}] Emitted data`, 0); + } else { + // we need to resolve the promise, perhaps this queryable doesn't return a result + // but hasn't produced an error + this.emit[this.InternalResolveEvent](result); } } catch (e) { this.log(`[request:${requestId}] Emitting error: "${e.message || e}"`, 3); // anything that throws we emit and continue + this.emit[this.InternalRejectEvent](e); this.error(e); this.log(`[request:${requestId}] Emitted error: "${e.message || e}"`, 3); @@ -161,8 +167,8 @@ export class Queryable extends Timeline implements IQu }, 0); return new Promise((resolve, reject) => { - this.on.data.replace(resolve); - this.on.error(reject); + this.on[this.InternalResolveEvent].replace(resolve); + this.on[this.InternalRejectEvent].replace(reject); }); } } @@ -172,7 +178,7 @@ export class Queryable extends Timeline implements IQu * The code is contained in invokable decorator */ // eslint-disable-next-line no-redeclare -export interface Queryable extends IInvokable {} +export interface Queryable extends IInvokable { } // this interface is required to stop the class from recursively referencing itself through the DefaultBehaviors type export interface IQueryableInternal extends Timeline, IInvokable { diff --git a/packages/sp-addinhelpers/sprestaddin.ts b/packages/sp-addinhelpers/sprestaddin.ts index 15bc4a5cb..5fc573885 100644 --- a/packages/sp-addinhelpers/sprestaddin.ts +++ b/packages/sp-addinhelpers/sprestaddin.ts @@ -8,10 +8,10 @@ import { Site, ISite } from "@pnp/sp/sites"; import { isUrlAbsolute, combine, + AssignFrom, } from "@pnp/core"; import { ISPQueryable } from "@pnp/sp"; -import { CopyFromQueryable } from "@pnp/queryable"; export class SPRestAddIn extends SPFI { @@ -62,7 +62,7 @@ export class SPRestAddIn extends SPFI { const instance = factory(url, urlPart); instance.query.set("@target", "'" + encodeURIComponent(hostWebUrl) + "'"); - instance.using(CopyFromQueryable(this._root)); + instance.using(AssignFrom(this._root)); return instance; } diff --git a/packages/sp/appcatalog/index.ts b/packages/sp/appcatalog/index.ts index 105ecdad5..40d01f367 100644 --- a/packages/sp/appcatalog/index.ts +++ b/packages/sp/appcatalog/index.ts @@ -1,8 +1,6 @@ -import { IWeb, Web, _Web } from "../webs/types.js"; -import { CopyFromQueryable } from "@pnp/queryable"; - -import "./web.js"; -import { AppCatalog, IAppCatalog } from "./types.js"; +import { SPFI } from "../fi"; +import { IWeb, Web } from "../webs/types.js"; +import { AssignFrom } from "@pnp/core"; export { IAppAddResult, @@ -12,16 +10,24 @@ export { AppCatalog, } from "./types.js"; -declare module "../webs/types" { - interface IWeb { - getTenantAppCatalog(): Promise; - } - interface _Web { - getTenantAppCatalog(): Promise; +declare module "../fi" { + interface SPFI { + getTenantAppCatalogWeb(): Promise; } } -_Web.prototype.getTenantAppCatalog = async function (this: IWeb): Promise { - const data: { CorporateCatalogUrl: string } = await Web(this.toUrl().replace(/\/_api\/.*$/i, ""), "/_api/SP_TenantSettings_Current").using(CopyFromQueryable(this))(); - return AppCatalog(data.CorporateCatalogUrl).using(CopyFromQueryable(this)); +SPFI.prototype.getTenantAppCatalogWeb = async function (this: SPFI): Promise { + + + return this.create(async (q) => { + + const data: { CorporateCatalogUrl: string } = await Web(q.toUrl().replace(/\/_api\/.*$/i, ""), "/_api/SP_TenantSettings_Current").using(AssignFrom(q))(); + + console.log(data); + + return Web(data.CorporateCatalogUrl).using(AssignFrom(q)); + + }); + + return null; }; diff --git a/packages/sp/appcatalog/types.ts b/packages/sp/appcatalog/types.ts index 56d675c27..3cb2052a4 100644 --- a/packages/sp/appcatalog/types.ts +++ b/packages/sp/appcatalog/types.ts @@ -6,11 +6,10 @@ import { SPCollection, } from "../spqueryable"; import { spPost } from "../operations.js"; -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; import { File, IFile } from "../files/types.js"; -import { CopyFromQueryable } from "@pnp/queryable"; - +import { AssignFrom } from "@pnp/core"; export class _AppCatalog extends _SPCollection { @@ -44,8 +43,8 @@ export class _AppCatalog extends _SPCollection { } else { - const listId = (await SPCollection(webUrl, "lists").using(CopyFromQueryable(this)).select("Id").filter("EntityTypeName eq 'AppCatalog'")())[0].Id; - const listItems = await SPCollection(webUrl, `lists/getById('${listId}')/items`).select("Id").filter("AppProductID eq '${id}'").top(1).using(CopyFromQueryable(this))(); + const listId = (await SPCollection(webUrl, "lists").using(AssignFrom(this)).select("Id").filter("EntityTypeName eq 'AppCatalog'")())[0].Id; + const listItems = await SPCollection(webUrl, `lists/getById('${listId}')/items`).select("Id").filter("AppProductID eq '${id}'").top(1).using(AssignFrom(this))(); if (listItems && listItems.length > 0) { diff --git a/packages/sp/clientside-pages/types.ts b/packages/sp/clientside-pages/types.ts index e4ea2a69f..1e7ae50a7 100644 --- a/packages/sp/clientside-pages/types.ts +++ b/packages/sp/clientside-pages/types.ts @@ -1,12 +1,12 @@ -import { body, headers, CopyFromQueryable } from "@pnp/queryable"; -import { getGUID, hOP, stringIsNullOrEmpty, objectDefinedNotNull, combine, isUrlAbsolute, isArray } from "@pnp/core"; +import { body, headers } from "@pnp/queryable"; +import { getGUID, hOP, stringIsNullOrEmpty, objectDefinedNotNull, combine, isUrlAbsolute, isArray, AssignFrom } from "@pnp/core"; import { IFile, IFileInfo } from "../files/types.js"; import { Item, IItem } from "../items/types.js"; import { _SPQueryable, ISPQueryable, SPQueryable, SPCollection } from "../spqueryable.js"; import { List } from "../lists/types.js"; -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; import { Web, IWeb } from "../webs/types.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; import { Site } from "../sites/types.js"; import { spPost } from "../operations.js"; import { getNextOrder, reindex } from "./funcs.js"; @@ -612,7 +612,7 @@ export class _ClientsidePage extends _SPQueryable { public async setAuthorById(authorId: number): Promise { const userLoginData = await SPCollection(extractWebUrl(this.toUrl()), "/_api/web/siteusers") - .using(CopyFromQueryable(this)) + .using(AssignFrom(this)) .filter(`Id eq ${authorId}`) .select("LoginName")<{ LoginName: string }[]>(); @@ -631,7 +631,7 @@ export class _ClientsidePage extends _SPQueryable { public async setAuthorByLoginName(authorLoginName: string): Promise { const userLoginData = await SPCollection(extractWebUrl(this.toUrl()), "/_api/web/siteusers") - .using(CopyFromQueryable(this)) + .using(AssignFrom(this)) .filter(`LoginName eq '${authorLoginName}'`) .select("UserPrincipalName", "Title")<{ UserPrincipalName: string; Title: string }[]>(); @@ -657,9 +657,9 @@ export class _ClientsidePage extends _SPQueryable { public async getItem(...selects: string[]): Promise { const initer = ClientsidePage(this, "/_api/lists/EnsureClientRenderedSitePagesLibrary").select("EnableModeration", "EnableMinorVersions", "Id"); const listData = await spPost<{ Id: string; "odata.id": string }>(initer); - const item = List(listData["odata.id"]).using(CopyFromQueryable(this)).items.getById(this.json.Id); + const item = List(listData["odata.id"]).using(AssignFrom(this)).items.getById(this.json.Id); const itemData: T = await item.select(...selects)(); - return Object.assign(Item(odataUrlFrom(itemData)).using(CopyFromQueryable(this)), itemData); + return Object.assign(Item(odataUrlFrom(itemData)).using(AssignFrom(this)), itemData); } protected getCanvasContent1(): string { @@ -880,7 +880,7 @@ const ClientsidePage = ( export const ClientsidePageFromFile = async (file: IFile): Promise => { const item = await file.getItem<{ Id: number }>(); - const page = ClientsidePage(extractWebUrl(file.toUrl()), "", { Id: item.Id }, true).using(CopyFromQueryable(file)); + const page = ClientsidePage(extractWebUrl(file.toUrl()), "", { Id: item.Id }, true).using(AssignFrom(file)); return page.load(); }; diff --git a/packages/sp/column-defaults/folder.ts b/packages/sp/column-defaults/folder.ts index b68dfc6b1..2dc6c8257 100644 --- a/packages/sp/column-defaults/folder.ts +++ b/packages/sp/column-defaults/folder.ts @@ -1,6 +1,6 @@ -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; -import { IResourcePath } from "../utils/toResourcePath.js"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; +import { IResourcePath } from "../utils/to-resource-path.js"; import { Web } from "../webs/types.js"; import "../lists/web.js"; import { _Folder, Folder } from "../folders/types.js"; diff --git a/packages/sp/column-defaults/list.ts b/packages/sp/column-defaults/list.ts index 5cdf5d79b..4e78a29dc 100644 --- a/packages/sp/column-defaults/list.ts +++ b/packages/sp/column-defaults/list.ts @@ -2,9 +2,9 @@ import { addProp, headers, body, TextParse } from "@pnp/queryable"; import { _List, List } from "../lists/types.js"; import { Folder } from "../folders/types.js"; import { IFieldDefault } from "./types.js"; -import { IResourcePath } from "../utils/toResourcePath.js"; +import { IResourcePath } from "../utils/to-resource-path.js"; import { combine, isArray } from "@pnp/core"; -import { escapeQueryStrValue } from "../utils/escapeQueryStrValue.js"; +import { escapeQueryStrValue } from "../utils/escape-query-str.js"; import { spPost } from "../operations.js"; import { SPCollection } from "../presets/all.js"; diff --git a/packages/sp/comments/item.ts b/packages/sp/comments/item.ts index 822455997..67743b31d 100644 --- a/packages/sp/comments/item.ts +++ b/packages/sp/comments/item.ts @@ -2,7 +2,7 @@ import { addProp } from "@pnp/queryable"; import { _Item, Item } from "../items/types.js"; import { Comments, IComments, ILikeData, ILikedByInformation } from "./types.js"; import { spPost } from "../operations.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; import { combine } from "@pnp/core"; import { SPQueryable } from "../spqueryable.js"; diff --git a/packages/sp/comments/types.ts b/packages/sp/comments/types.ts index 00a05996c..4a74f6d84 100644 --- a/packages/sp/comments/types.ts +++ b/packages/sp/comments/types.ts @@ -4,7 +4,7 @@ import { spInvokableFactory, _SPInstance, } from "../spqueryable.js"; -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; import { body } from "@pnp/queryable"; import { spPost } from "../operations.js"; diff --git a/packages/sp/files/types.ts b/packages/sp/files/types.ts index 51b9d630d..3f0bc8259 100644 --- a/packages/sp/files/types.ts +++ b/packages/sp/files/types.ts @@ -10,12 +10,12 @@ import { deleteableWithETag, } from "../spqueryable.js"; import { Item, IItem } from "../items/index.js"; -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; import { defaultPath } from "../decorators.js"; import { spPost } from "../operations.js"; -import { escapeQueryStrValue } from "../utils/escapeQueryStrValue.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; -import { toResourcePath } from "../utils/toResourcePath.js"; +import { escapeQueryStrValue } from "../utils/escape-query-str.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; +import { toResourcePath } from "../utils/to-resource-path.js"; /** * Describes a collection of File objects diff --git a/packages/sp/files/web.ts b/packages/sp/files/web.ts index 509c2f784..94e5c40bc 100644 --- a/packages/sp/files/web.ts +++ b/packages/sp/files/web.ts @@ -1,6 +1,6 @@ import { _Web } from "../webs/types.js"; import { File, IFile } from "./types.js"; -import { escapeQueryStrValue } from "../utils/escapeQueryStrValue.js"; +import { escapeQueryStrValue } from "../utils/escape-query-str.js"; declare module "../webs/types" { interface _Web { diff --git a/packages/sp/folders/types.ts b/packages/sp/folders/types.ts index 6a0d105ef..c1fa57de2 100644 --- a/packages/sp/folders/types.ts +++ b/packages/sp/folders/types.ts @@ -9,13 +9,13 @@ import { ISPInstance, IDeleteableWithETag, } from "../spqueryable.js"; -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; import { IItem, Item } from "../items/types.js"; import { defaultPath } from "../decorators.js"; import { spPost, spPostMerge } from "../operations.js"; -import { escapeQueryStrValue } from "../utils/escapeQueryStrValue.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; -import { toResourcePath, IResourcePath } from "../utils/toResourcePath.js"; +import { escapeQueryStrValue } from "../utils/escape-query-str.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; +import { toResourcePath, IResourcePath } from "../utils/to-resource-path.js"; @defaultPath("folders") export class _Folders extends _SPCollection { diff --git a/packages/sp/folders/web.ts b/packages/sp/folders/web.ts index 32f322358..904d7bdee 100644 --- a/packages/sp/folders/web.ts +++ b/packages/sp/folders/web.ts @@ -1,7 +1,7 @@ import { addProp } from "@pnp/queryable"; import { _Web } from "../webs/types.js"; import { Folders, IFolders, Folder, IFolder } from "./types.js"; -import { escapeQueryStrValue } from "../utils/escapeQueryStrValue.js"; +import { escapeQueryStrValue } from "../utils/escape-query-str.js"; declare module "../webs/types" { interface _Web { diff --git a/packages/sp/index.ts b/packages/sp/index.ts index 017c7835a..994ffc655 100644 --- a/packages/sp/index.ts +++ b/packages/sp/index.ts @@ -11,18 +11,10 @@ export { export * from "./types.js"; -export { - extractWebUrl, -} from "./utils/extractweburl.js"; - -export { - stripInvalidFileFolderChars, - containsInvalidFileFolderChars, -} from "./utils/file-names.js"; - -export { - odataUrlFrom, -} from "./utils/odataUrlFrom.js"; +export * from "./utils/escape-query-str.js"; +export * from "./utils/extract-web-url.js"; +export * from "./utils/file-names.js"; +export * from "./utils/odata-url-from.js"; export * from "./behaviors/defaults.js"; export * from "./behaviors/telemetry.js"; diff --git a/packages/sp/items/types.ts b/packages/sp/items/types.ts index bd0114941..f60d78555 100644 --- a/packages/sp/items/types.ts +++ b/packages/sp/items/types.ts @@ -9,13 +9,13 @@ import { SPInstance, ISPInstance, } from "../spqueryable.js"; -import { hOP } from "@pnp/core"; +import { hOP, AssignFrom } from "@pnp/core"; import { IListItemFormUpdateValue, List } from "../lists/types.js"; -import { body, headers, parseBinderWithErrorCheck, parseODataJSON, CopyFromQueryable } from "@pnp/queryable"; +import { body, headers, parseBinderWithErrorCheck, parseODataJSON } from "@pnp/queryable"; import { IList } from "../lists/index.js"; import { defaultPath } from "../decorators.js"; import { spPost } from "../operations.js"; -import { IResourcePath } from "../utils/toResourcePath.js"; +import { IResourcePath } from "../utils/to-resource-path.js"; /** * Describes a collection of Item objects @@ -309,7 +309,7 @@ export class PagedItemCollection { public getNext(): Promise> { if (this.hasNext) { - const items = Items(this.nextUrl, null).using(CopyFromQueryable(this.parent)); + const items = Items(this.nextUrl, null).using(AssignFrom(this.parent)); return items.getPaged(); } diff --git a/packages/sp/lists/types.ts b/packages/sp/lists/types.ts index cd6ab935e..80ff8870f 100644 --- a/packages/sp/lists/types.ts +++ b/packages/sp/lists/types.ts @@ -12,17 +12,17 @@ import { IDeleteableWithETag, } from "../spqueryable.js"; import { IChangeQuery } from "../types.js"; -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; import { defaultPath } from "../decorators.js"; import { spPost, spPostMerge } from "../operations.js"; -import { escapeQueryStrValue } from "../utils/escapeQueryStrValue.js"; +import { escapeQueryStrValue } from "../utils/escape-query-str.js"; import { IBasePermissions } from "../security/types.js"; import { IFieldInfo } from "../fields/types.js"; import { IFormInfo } from "../forms/types.js"; import { IFolderInfo } from "../folders/types.js"; import { IViewInfo } from "../views/types.js"; import { IUserCustomActionInfo } from "../user-custom-actions/types.js"; -import { IResourcePath, toResourcePath } from "../utils/toResourcePath.js"; +import { IResourcePath, toResourcePath } from "../utils/to-resource-path.js"; @defaultPath("lists") export class _Lists extends _SPCollection { diff --git a/packages/sp/lists/web.ts b/packages/sp/lists/web.ts index 56ecfc83c..0844733dd 100644 --- a/packages/sp/lists/web.ts +++ b/packages/sp/lists/web.ts @@ -1,9 +1,9 @@ import { addProp } from "@pnp/queryable"; import { _Web, Web } from "../webs/types.js"; import { Lists, ILists, IList, List } from "./types.js"; -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; import { ISPCollection, SPCollection } from "../spqueryable.js"; -import { escapeQueryStrValue } from "../utils/escapeQueryStrValue.js"; +import { escapeQueryStrValue } from "../utils/escape-query-str.js"; declare module "../webs/types" { interface _Web { diff --git a/packages/sp/presets/all.ts b/packages/sp/presets/all.ts index 27d8cdca6..079dec563 100644 --- a/packages/sp/presets/all.ts +++ b/packages/sp/presets/all.ts @@ -48,7 +48,7 @@ export * from "../hubsites/index.js"; export * from "../items/index.js"; export * from "../lists/index.js"; export * from "../navigation/index.js"; -export * from "../utils/odataUrlFrom.js"; +export * from "../utils/odata-url-from.js"; export * from "../profiles/index.js"; export * from "../regional-settings/index.js"; export * from "../related-items/index.js"; diff --git a/packages/sp/profiles/types.ts b/packages/sp/profiles/types.ts index 5b6a40ac2..8739df92e 100644 --- a/packages/sp/profiles/types.ts +++ b/packages/sp/profiles/types.ts @@ -7,10 +7,11 @@ import { spInvokableFactory, _SPQueryable, } from "../spqueryable.js"; -import { body, CopyFromQueryable } from "@pnp/queryable"; +import { body } from "@pnp/queryable"; import { PrincipalType, PrincipalSource } from "../types.js"; import { defaultPath } from "../decorators.js"; import { spPost } from "../operations.js"; +import { AssignFrom } from "@pnp/core"; export class _Profiles extends _SPInstance { @@ -25,8 +26,8 @@ export class _Profiles extends _SPInstance { constructor(baseUrl: string | ISPQueryable, path = "_api/sp.userprofiles.peoplemanager") { super(baseUrl, path); - this.clientPeoplePickerQuery = (new ClientPeoplePickerQuery(baseUrl)).using(CopyFromQueryable(this)); - this.profileLoader = (new ProfileLoader(baseUrl)).using(CopyFromQueryable(this)); + this.clientPeoplePickerQuery = (new ClientPeoplePickerQuery(baseUrl)).using(AssignFrom(this)); + this.profileLoader = (new ProfileLoader(baseUrl)).using(AssignFrom(this)); } /** diff --git a/packages/sp/related-items/types.ts b/packages/sp/related-items/types.ts index 8a617e300..316de27a2 100644 --- a/packages/sp/related-items/types.ts +++ b/packages/sp/related-items/types.ts @@ -1,5 +1,5 @@ import { _SPQueryable, ISPQueryable } from "../spqueryable.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; import { defaultPath } from "../decorators.js"; import { spPost } from "../operations.js"; import { body } from "@pnp/queryable"; diff --git a/packages/sp/sharing/funcs.ts b/packages/sp/sharing/funcs.ts index 6194fabb8..63f232460 100644 --- a/packages/sp/sharing/funcs.ts +++ b/packages/sp/sharing/funcs.ts @@ -1,7 +1,7 @@ import { body } from "@pnp/queryable"; import { jsS } from "@pnp/core"; import { SPCollection, SPInstance } from "../spqueryable.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; import { Web } from "../webs/types.js"; import { ShareableQueryable, diff --git a/packages/sp/site-designs/types.ts b/packages/sp/site-designs/types.ts index 450d2d955..19143f806 100644 --- a/packages/sp/site-designs/types.ts +++ b/packages/sp/site-designs/types.ts @@ -1,5 +1,5 @@ import { ISPQueryable, _SPQueryable } from "../spqueryable.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; import { headers, body } from "@pnp/queryable"; import { spPost } from "../operations.js"; import { hOP } from "@pnp/core"; diff --git a/packages/sp/site-scripts/list.ts b/packages/sp/site-scripts/list.ts index d2cb14c97..c35a53dec 100644 --- a/packages/sp/site-scripts/list.ts +++ b/packages/sp/site-scripts/list.ts @@ -3,7 +3,7 @@ import { _List, List } from "../lists/types.js"; import { SiteScripts } from "./types.js"; import "../folders/list.js"; import { Web } from "../webs/types.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; declare module "../lists/types" { interface _List { diff --git a/packages/sp/site-scripts/types.ts b/packages/sp/site-scripts/types.ts index 0fac47f37..303eb5a4f 100644 --- a/packages/sp/site-scripts/types.ts +++ b/packages/sp/site-scripts/types.ts @@ -1,9 +1,9 @@ import { body } from "@pnp/queryable"; import { spPost } from "../operations.js"; import { ISPQueryable, _SPQueryable } from "../spqueryable.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; -import { escapeQueryStrValue } from "../utils/escapeQueryStrValue.js"; +import { escapeQueryStrValue } from "../utils/escape-query-str.js"; export class _SiteScripts extends _SPQueryable { diff --git a/packages/sp/site-users/web.ts b/packages/sp/site-users/web.ts index 90eb151db..90b5d9e0d 100644 --- a/packages/sp/site-users/web.ts +++ b/packages/sp/site-users/web.ts @@ -1,7 +1,7 @@ import { addProp, body } from "@pnp/queryable"; import { _Web, Web } from "../webs/types.js"; import { ISiteUsers, SiteUsers, ISiteUser, SiteUser, IWebEnsureUserResult } from "./types.js"; -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; import { spPost } from "../operations.js"; declare module "../webs/types" { diff --git a/packages/sp/sites/types.ts b/packages/sp/sites/types.ts index f81f5a9b3..c1ab5d43b 100644 --- a/packages/sp/sites/types.ts +++ b/packages/sp/sites/types.ts @@ -1,13 +1,13 @@ import { _SPInstance, spInvokableFactory, SPQueryable } from "../spqueryable.js"; import { defaultPath } from "../decorators.js"; import { Web, IWeb } from "../webs/types.js"; -import { hOP } from "@pnp/core"; -import { body, CopyFromQueryable } from "@pnp/queryable"; -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; +import { AssignFrom, hOP } from "@pnp/core"; +import { body } from "@pnp/queryable"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; import { spPost } from "../operations.js"; -import { escapeQueryStrValue } from "../utils/escapeQueryStrValue.js"; +import { escapeQueryStrValue } from "../utils/escape-query-str.js"; import { IChangeQuery } from "../types.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; import { emptyGuid } from "../types.js"; @defaultPath("_api/site") @@ -42,7 +42,7 @@ export class _Site extends _SPInstance { const data = await spPost(Site(this, `openWebById('${webId}')`)); return { data, - web: Web(extractWebUrl(odataUrlFrom(data))).using(CopyFromQueryable(this)), + web: Web(extractWebUrl(odataUrlFrom(data))).using(AssignFrom(this)), }; } @@ -52,7 +52,7 @@ export class _Site extends _SPInstance { */ public async getRootWeb(): Promise { const web = await this.rootWeb.select("Url")<{ Url: string }>(); - return Web(web.Url).using(CopyFromQueryable(this)); + return Web(web.Url).using(AssignFrom(this)); } /** diff --git a/packages/sp/spqueryable.ts b/packages/sp/spqueryable.ts index 368bcf508..cb3ec7283 100644 --- a/packages/sp/spqueryable.ts +++ b/packages/sp/spqueryable.ts @@ -1,5 +1,5 @@ -import { combine, isUrlAbsolute } from "@pnp/core"; -import { IInvokable, Queryable, queryableFactory, CopyFromQueryable } from "@pnp/queryable"; +import { AssignFrom, combine, isUrlAbsolute } from "@pnp/core"; +import { IInvokable, Queryable, queryableFactory } from "@pnp/queryable"; import { spPostDelete, spPostDeleteETag } from "./operations.js"; export interface ISPConstructor { @@ -129,7 +129,7 @@ export class _SPQueryable extends Queryable { const parent = factory(baseUrl, path); if (typeof baseUrl === "string") { - parent.using(CopyFromQueryable(this)); + parent.using(AssignFrom(this)); } const t = "@target"; diff --git a/packages/sp/sputilities/types.ts b/packages/sp/sputilities/types.ts index 19ef25294..783784e79 100644 --- a/packages/sp/sputilities/types.ts +++ b/packages/sp/sputilities/types.ts @@ -1,7 +1,7 @@ import { body } from "@pnp/queryable"; import { _SPQueryable, spInvokableFactory, ISPQueryable } from "../spqueryable.js"; import { IPrincipalInfo, PrincipalType, PrincipalSource } from "../types.js"; -import { extractWebUrl } from "../utils/extractweburl.js"; +import { extractWebUrl } from "../utils/extract-web-url.js"; import { spPost } from "../operations.js"; export class _Utilities extends _SPQueryable implements IUtilities { diff --git a/packages/sp/utils/escapeQueryStrValue.ts b/packages/sp/utils/escape-query-str.ts similarity index 100% rename from packages/sp/utils/escapeQueryStrValue.ts rename to packages/sp/utils/escape-query-str.ts diff --git a/packages/sp/utils/extractweburl.ts b/packages/sp/utils/extract-web-url.ts similarity index 100% rename from packages/sp/utils/extractweburl.ts rename to packages/sp/utils/extract-web-url.ts diff --git a/packages/sp/utils/odataUrlFrom.ts b/packages/sp/utils/odata-url-from.ts similarity index 96% rename from packages/sp/utils/odataUrlFrom.ts rename to packages/sp/utils/odata-url-from.ts index 3659700e6..ca6c0b70d 100644 --- a/packages/sp/utils/odataUrlFrom.ts +++ b/packages/sp/utils/odata-url-from.ts @@ -1,5 +1,5 @@ import { combine, hOP, isUrlAbsolute } from "@pnp/core"; -import { extractWebUrl } from "./extractweburl.js"; +import { extractWebUrl } from "./extract-web-url.js"; export function odataUrlFrom(candidate: any): string { diff --git a/packages/sp/utils/toResourcePath.ts b/packages/sp/utils/to-resource-path.ts similarity index 100% rename from packages/sp/utils/toResourcePath.ts rename to packages/sp/utils/to-resource-path.ts diff --git a/packages/sp/webs/types.ts b/packages/sp/webs/types.ts index 26d106d2b..06b517d6e 100644 --- a/packages/sp/webs/types.ts +++ b/packages/sp/webs/types.ts @@ -1,4 +1,5 @@ -import { body, CopyFromQueryable } from "@pnp/queryable"; +import { AssignFrom } from "@pnp/core"; +import { body } from "@pnp/queryable"; import { _SPCollection, spInvokableFactory, @@ -12,9 +13,9 @@ import { } from "../spqueryable.js"; import { defaultPath } from "../decorators.js"; import { IChangeQuery } from "../types.js"; -import { odataUrlFrom } from "../utils/odataUrlFrom.js"; +import { odataUrlFrom } from "../utils/odata-url-from.js"; import { spPost, spPostMerge } from "../operations.js"; -import { escapeQueryStrValue } from "../utils/escapeQueryStrValue.js"; +import { escapeQueryStrValue } from "../utils/escape-query-str.js"; @defaultPath("webs") export class _Webs extends _SPCollection { @@ -46,7 +47,7 @@ export class _Webs extends _SPCollection { return { data, - web: Web(odataUrlFrom(data).replace(/_api\/web\/?/i, "")).using(CopyFromQueryable(this)), + web: Web(odataUrlFrom(data).replace(/_api\/web\/?/i, "")).using(AssignFrom(this)), }; } } @@ -93,7 +94,7 @@ export class _Web extends _SPInstance { const { Url, ParentWeb } = await this.select("Url", "ParentWeb/ServerRelativeUrl").expand("ParentWeb")<{ Url: string; ParentWeb: { ServerRelativeUrl: string } }>(); if (ParentWeb?.ServerRelativeUrl) { return Web(Url.substring(0, Url.indexOf(ParentWeb.ServerRelativeUrl) + ParentWeb.ServerRelativeUrl.length)) - .using(CopyFromQueryable(this)); + .using(AssignFrom(this)); } return null; } diff --git a/test/core/timeline.ts b/test/core/timeline.ts new file mode 100644 index 000000000..a3ab8543e --- /dev/null +++ b/test/core/timeline.ts @@ -0,0 +1,205 @@ +import { Timeline, asyncReduce } from "@pnp/core"; +import { expect } from "chai"; + +const TestingMoments = { + first: asyncReduce<(a: number) => Promise<[number]>>(), + second: asyncReduce<(a: number) => Promise<[number]>>(), +} as const; + +class TestTimeline extends Timeline { + + private InternalResolveEvent = Symbol.for("Resolve"); + private InternalRejectEvent = Symbol.for("Reject"); + + constructor() { + super(TestingMoments); + } + + protected async execute(init?: any): Promise { + + this.log("Starting", 0); + + setTimeout(async () => { + + try { + + // eslint-disable-next-line prefer-const + let [value] = await this.emit.first(init); + + [value] = await this.emit.second(value); + + this.emit[this.InternalResolveEvent](value); + + } catch (e) { + + this.emit[this.InternalRejectEvent](e); + } + }, 0); + + return new Promise((resolve, reject) => { + this.on[this.InternalResolveEvent].replace(resolve); + this.on[this.InternalRejectEvent].replace(reject); + }); + } + + public go(startValue = 0): Promise { + return this.start(startValue); + } +} + +describe("Timeline", function () { + + it("Should process moments", async function () { + + const tl = new TestTimeline(); + + tl.on.first(async (n) => [++n]); + + tl.on.second(async (n) => [++n]); + + const h = await tl.go(0); + + return expect(h).to.eq(2); + }); + + it("Should process moments 2", async function () { + + const tl = new TestTimeline(); + + tl.on.first(async (n) => [++n]); + + tl.on.second(async (n) => [++n]); + tl.on.second(async (n) => [++n]); + tl.on.second(async (n) => [++n]); + tl.on.second(async (n) => [++n]); + tl.on.second(async (n) => [++n]); + tl.on.second(async (n) => [++n]); + + const h = await tl.go(0); + + return expect(h).to.eq(7); + }); + + it("Prepend works as expected", function () { + + const tl = new TestTimeline(); + + const f1 = async (n) => n + 2; + const f2 = async (n) => n + 3; + const f3 = async (n) => n + 5; + + tl.on.first(f1); + tl.on.first(f2); + tl.on.first.prepend(f3); + + const observers = tl.on.first.toArray(); + + expect(observers[0]).to.eq(f3); + expect(observers[1]).to.eq(f1); + expect(observers[2]).to.eq(f2); + }); + + it("Clear works as expected", function () { + + const tl = new TestTimeline(); + + const f1 = async (n) => n + 2; + const f2 = async (n) => n + 3; + const f3 = async (n) => n + 5; + + tl.on.first(f1); + tl.on.first(f2); + tl.on.first(f3); + + const observers = tl.on.first.toArray(); + + expect(observers).length(3); + + tl.on.first.clear(); + + const observers2 = tl.on.first.toArray(); + + expect(observers2).length(0); + }); + + it("Replace works as expected", function () { + + const tl = new TestTimeline(); + + const f1 = async (n) => n + 2; + const f2 = async (n) => n + 3; + const f3 = async (n) => n + 5; + + tl.on.first(f1); + tl.on.first(f2); + tl.on.first(f3); + + const observers = tl.on.first.toArray(); + + expect(observers).length(3); + + tl.on.first.replace(f1); + + const observers2 = tl.on.first.toArray(); + + expect(observers2).length(1); + expect(observers2[0]).to.eq(f1); + }); + + it("Logging works as expected", function () { + + const tl = new TestTimeline(); + + const messages = []; + + tl.on.log((message) => { + messages.push(message); + }); + + tl.log("Test 1", 0); + + tl.log("Test 2", 0); + + expect(messages.length).to.eq(2); + }); + + it("Lifecycle works as expected", async function () { + + const tl = new TestTimeline(); + + const tracker = []; + + tl.on.init(() => { + + tracker.push(1); + }); + + tl.on.first(async (v) => { + + tracker.push(2); + + return [v]; + }); + + tl.on.second(async (v) => { + + tracker.push(3); + + return [v]; + }); + + tl.on.dispose(() => { + + tracker.push(4); + }); + + const h = await tl.go(0); + + expect(h).to.eq(0); + expect(tracker.length).to.eq(4); + expect(tracker[0]).to.eq(1); + expect(tracker[1]).to.eq(2); + expect(tracker[2]).to.eq(3); + expect(tracker[3]).to.eq(4); + }); +}); diff --git a/test/queryable/add-prop.ts b/test/queryable/add-prop.ts new file mode 100644 index 000000000..f75ee27ef --- /dev/null +++ b/test/queryable/add-prop.ts @@ -0,0 +1,24 @@ +import { expect } from "chai"; +import { + addProp, +} from "@pnp/queryable"; +import "@pnp/sp/webs"; + +describe("add-prop", function () { + + it("Should add a property to an object", async function () { + + function tester() { + this.name = "Testing"; + } + + addProp(tester, "prop", (_o, path) => { + return path; + }, "path-value"); + + const y = new tester(); + + expect(y).to.have.property("name", "Testing"); + expect(y).to.have.property("prop", "path-value"); + }); +}); diff --git a/test/queryable/behaviors.ts b/test/queryable/behaviors.ts index eeff68348..dbd5a879d 100644 --- a/test/queryable/behaviors.ts +++ b/test/queryable/behaviors.ts @@ -1,14 +1,25 @@ import { assert, expect } from "chai"; -import { Caching, CachingPessimisticRefresh } from "@pnp/queryable"; +import { + Caching, + CachingPessimisticRefresh, + BearerToken, + Queryable, + InjectHeaders, + Timeout, + ThrowErrors, +} from "@pnp/queryable"; import { spfi } from "@pnp/sp"; import { SPDefault } from "@pnp/nodejs"; +import { AbortController } from "node-abort-controller"; +import { default as nodeFetch } from "node-fetch"; import { testSettings } from "../main.js"; import "@pnp/sp/webs"; describe("Behaviors", function () { - describe("Queryable", function () { + + if (testSettings.enableWebTests) { it("CachingPessimistic", async function () { try { @@ -71,5 +82,74 @@ describe("Behaviors", function () { assert.fail(`Behaviors/Queryable/Caching - ${err.message}`); } }); + } + + it("Bearer Token", async function () { + + const query = new Queryable("https://bing.com"); + query.using(BearerToken("!!token!!")); + + query.on.send.replace((url, init) => { + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(init.headers).to.not.be.undefined.and.to.not.be.null; + + expect(init.headers).to.have.property("Authorization", "Bearer !!token!!"); + + return null; + }); + + return query(); + }); + + it("Inject Headers", async function () { + + const query = new Queryable("https://bing.com"); + query.using(InjectHeaders({ + "header1": "header1-value", + "header2": "header2-value", + })); + + query.on.send.replace((url, init) => { + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(init.headers).to.not.be.undefined.and.to.not.be.null; + + expect(init.headers).to.have.property("header1", "header1-value"); + + expect(init.headers).to.have.property("header2", "header2-value"); + + return null; + }); + + return query(); + }); + + it("Timeout", async function () { + + // must patch in node < 15 + const controller = new AbortController(); + + const query = new Queryable("https://bing.com"); + query.using(Timeout(controller.signal)); + + query.on.send.replace(async (url, init) => nodeFetch(url, init)); + + query.using(ThrowErrors()); + + try { + + controller.abort(); + await query(); + + expect.fail("Timeout should cause error and we end up in catch before this line."); + + } catch (e) { + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(e).to.not.be.null; + + expect(e).property("name").to.not.eq("AssertionError"); + } }); }); diff --git a/test/queryable/invokable.ts b/test/queryable/invokable.ts new file mode 100644 index 000000000..e59aef749 --- /dev/null +++ b/test/queryable/invokable.ts @@ -0,0 +1,35 @@ +import { expect } from "chai"; +import { + invokable, + IInvokable, +} from "@pnp/queryable"; + +const value = "Test Result"; +const value2 = "Test2 Values"; + +@invokable(async () => value) +class InvokableTest { } +// eslint-disable-next-line no-redeclare +interface InvokableTest extends IInvokable { } + +@invokable(async () => value2) +class InvokableTest2 extends InvokableTest { } +// eslint-disable-next-line no-redeclare +interface InvokableTest2 extends IInvokable { } + +describe("invokable", function () { + + it("works", async function () { + + const obj = new InvokableTest(); + const v = await obj(); + expect(v).to.eq(value); + }); + + it("correctly overrides in inheriting classes", async function () { + + const obj = new InvokableTest2(); + const v = await obj(); + expect(v).to.eq(value2); + }); +}); diff --git a/test/queryable/queryable.ts b/test/queryable/queryable.ts new file mode 100644 index 000000000..c80f6b07b --- /dev/null +++ b/test/queryable/queryable.ts @@ -0,0 +1,115 @@ +import { expect } from "chai"; +import { + Queryable, + BearerToken, + InjectHeaders, + DefaultParse, +} from "@pnp/queryable"; + +describe("Queryable", function () { + + it("Lifecycle works as expected", async function () { + + const tracker = []; + + const q = new Queryable("https://bing.com"); + + q.on.init(() => { + tracker.push(1); + }); + + q.on.pre(async (url, init, result) => { + tracker.push(2); + return [url, init, result]; + }); + + q.on.auth(async (url, init) => { + tracker.push(3); + return [url, init]; + }); + + q.on.send(async () => { + tracker.push(4); + return null; + }); + + q.on.parse(async (url, response, result) => { + tracker.push(5); + return [url, response, result]; + }); + + q.on.post(async (url, result) => { + tracker.push(6); + return [url, result]; + }); + + q.on.dispose(() => { + tracker.push(7); + }); + + await q(); + + expect(tracker.length).to.eq(7); + expect(tracker[0]).to.eq(1); + expect(tracker[1]).to.eq(2); + expect(tracker[2]).to.eq(3); + expect(tracker[3]).to.eq(4); + expect(tracker[4]).to.eq(5); + expect(tracker[5]).to.eq(6); + expect(tracker[6]).to.eq(7); + }); + + it("Observer inhertance", function () { + + const q = new Queryable("https://bing.com"); + + q.using( + BearerToken("token"), + DefaultParse(), + InjectHeaders({ + "X-name": "value", + })); + + const q2 = new Queryable(q); + + // directly inherited with no changes should equal parent + expect((q2).observers).to.be.equal((q).observers); + + q.on.post(async (url, result) => [url, result]); + + // addition to parent should appear to child as they share a ref + expect((q2).observers).to.be.equal((q).observers); + + q2.on.post(async (url, result) => [url, result]); + + // ref to parent is broken due to edit of child + expect((q2).observers).to.not.be.equal((q).observers); + }); + + it("Url manipulation", function () { + + const q = new Queryable("https://bing.com"); + + const q2 = new Queryable(q); + + expect(q.toUrl()).to.be.eq(q2.toUrl()); + + const q3 = new Queryable(q, "path1"); + const q4 = new Queryable(q3, "path2"); + + expect(q3.toUrl()).to.be.eq("https://bing.com/path1"); + expect(q4.toUrl()).to.be.eq("https://bing.com/path1/path2"); + + q4.concat("(path3)"); + + expect(q4.toUrl()).to.be.eq("https://bing.com/path1/path2(path3)"); + + q.query.set("key", "value"); + + expect(q.toRequestUrl()).to.be.eq("https://bing.com?key=value"); + + q.query.set("key2", "value2?"); + + expect(q.toRequestUrl()).to.be.eq("https://bing.com?key=value&key2=value2%3F"); + }); +});