@fluid-experimental/dds-interceptions 2.0.0-dev-rc.2.0.0.245554
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/.eslintrc.cjs +14 -0
- package/.mocharc.cjs +12 -0
- package/CHANGELOG.md +93 -0
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/api-extractor-cjs.json +8 -0
- package/api-extractor-lint.json +4 -0
- package/api-extractor.json +4 -0
- package/api-report/dds-interceptions.api.md +24 -0
- package/dist/dds-interceptions-alpha.d.ts +13 -0
- package/dist/dds-interceptions-beta.d.ts +19 -0
- package/dist/dds-interceptions-public.d.ts +19 -0
- package/dist/dds-interceptions-untrimmed.d.ts +67 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/map/index.d.ts +7 -0
- package/dist/map/index.d.ts.map +1 -0
- package/dist/map/index.js +12 -0
- package/dist/map/index.js.map +1 -0
- package/dist/map/sharedDirectoryWithInterception.d.ts +29 -0
- package/dist/map/sharedDirectoryWithInterception.d.ts.map +1 -0
- package/dist/map/sharedDirectoryWithInterception.js +117 -0
- package/dist/map/sharedDirectoryWithInterception.js.map +1 -0
- package/dist/map/sharedMapWithInterception.d.ts +23 -0
- package/dist/map/sharedMapWithInterception.d.ts.map +1 -0
- package/dist/map/sharedMapWithInterception.js +49 -0
- package/dist/map/sharedMapWithInterception.js.map +1 -0
- package/dist/package.json +3 -0
- package/dist/sequence/index.d.ts +6 -0
- package/dist/sequence/index.d.ts.map +1 -0
- package/dist/sequence/index.js +10 -0
- package/dist/sequence/index.js.map +1 -0
- package/dist/sequence/sharedStringWithInterception.d.ts +27 -0
- package/dist/sequence/sharedStringWithInterception.d.ts.map +1 -0
- package/dist/sequence/sharedStringWithInterception.js +207 -0
- package/dist/sequence/sharedStringWithInterception.js.map +1 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/lib/dds-interceptions-alpha.d.ts +13 -0
- package/lib/dds-interceptions-beta.d.ts +19 -0
- package/lib/dds-interceptions-public.d.ts +19 -0
- package/lib/dds-interceptions-untrimmed.d.ts +67 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -0
- package/lib/map/index.d.ts +7 -0
- package/lib/map/index.d.ts.map +1 -0
- package/lib/map/index.js +7 -0
- package/lib/map/index.js.map +1 -0
- package/lib/map/sharedDirectoryWithInterception.d.ts +29 -0
- package/lib/map/sharedDirectoryWithInterception.d.ts.map +1 -0
- package/lib/map/sharedDirectoryWithInterception.js +113 -0
- package/lib/map/sharedDirectoryWithInterception.js.map +1 -0
- package/lib/map/sharedMapWithInterception.d.ts +23 -0
- package/lib/map/sharedMapWithInterception.d.ts.map +1 -0
- package/lib/map/sharedMapWithInterception.js +45 -0
- package/lib/map/sharedMapWithInterception.js.map +1 -0
- package/lib/sequence/index.d.ts +6 -0
- package/lib/sequence/index.d.ts.map +1 -0
- package/lib/sequence/index.js +6 -0
- package/lib/sequence/index.js.map +1 -0
- package/lib/sequence/sharedStringWithInterception.d.ts +27 -0
- package/lib/sequence/sharedStringWithInterception.d.ts.map +1 -0
- package/lib/sequence/sharedStringWithInterception.js +203 -0
- package/lib/sequence/sharedStringWithInterception.js.map +1 -0
- package/lib/test/sharedDirectoryWithInterception.spec.js +282 -0
- package/lib/test/sharedDirectoryWithInterception.spec.js.map +1 -0
- package/lib/test/sharedMapWithInterception.spec.js +105 -0
- package/lib/test/sharedMapWithInterception.spec.js.map +1 -0
- package/lib/test/sharedStringWithInterception.spec.js +147 -0
- package/lib/test/sharedStringWithInterception.spec.js.map +1 -0
- package/package.json +151 -0
- package/prettier.config.cjs +8 -0
- package/src/index.ts +7 -0
- package/src/map/index.ts +7 -0
- package/src/map/sharedDirectoryWithInterception.ts +172 -0
- package/src/map/sharedMapWithInterception.ts +58 -0
- package/src/sequence/index.ts +6 -0
- package/src/sequence/sharedStringWithInterception.ts +288 -0
- package/tsconfig.cjs.json +7 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sharedStringWithInterception.spec.js","sourceRoot":"","sources":["../../src/test/sharedStringWithInterception.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { strict as assert } from \"assert\";\nimport { PropertySet } from \"@fluidframework/merge-tree\";\nimport { MockFluidDataStoreRuntime } from \"@fluidframework/test-runtime-utils\";\nimport { SharedString, SharedStringFactory } from \"@fluidframework/sequence\";\nimport { IFluidDataStoreContext } from \"@fluidframework/runtime-definitions\";\nimport { createSharedStringWithInterception } from \"../sequence/index.js\";\n\ndescribe(\"Shared String with Interception\", () => {\n\t/**\n\t * The following tests test simple user attribution in SharedString with interception.\n\t * In the callback function of the SharedString with interception, it adds the user\n\t * information to the passed properties and returns it.\n\t */\n\tdescribe(\"Simple User Attribution\", () => {\n\t\tconst userAttributes = { userId: \"Fake User\" };\n\t\tconst documentId = \"fakeId\";\n\t\tlet sharedString: SharedString;\n\t\tlet dataStoreContext: IFluidDataStoreContext;\n\n\t\tfunction orderSequentially(callback: () => void): void {\n\t\t\tcallback();\n\t\t}\n\n\t\t// Interception function that adds userProps to the passed props and returns.\n\t\tfunction propertyInterceptionCb(props?: PropertySet): PropertySet {\n\t\t\tconst newProps = { ...props, ...userAttributes };\n\t\t\treturn newProps;\n\t\t}\n\n\t\t// Function that verifies that the given shared string has correct value and the right properties at\n\t\t// the given position.\n\t\tfunction verifyString(\n\t\t\tss: SharedString,\n\t\t\ttext: string,\n\t\t\tprops: PropertySet,\n\t\t\tposition: number,\n\t\t) {\n\t\t\tassert.equal(ss.getText(), text, \"The retrieved text should match the inserted text\");\n\t\t\tassert.deepEqual(\n\t\t\t\t{ ...ss.getPropertiesAtPosition(position) },\n\t\t\t\t{ ...props },\n\t\t\t\t\"The properties set via the interception callback should exist\",\n\t\t\t);\n\t\t}\n\n\t\tbeforeEach(() => {\n\t\t\tconst dataStoreRuntime = new MockFluidDataStoreRuntime();\n\t\t\tsharedString = new SharedString(\n\t\t\t\tdataStoreRuntime,\n\t\t\t\tdocumentId,\n\t\t\t\tSharedStringFactory.Attributes,\n\t\t\t);\n\n\t\t\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\t\t\tdataStoreContext = {\n\t\t\t\tcontainerRuntime: { orderSequentially },\n\t\t\t} as IFluidDataStoreContext;\n\t\t});\n\n\t\tit(\"should be able to intercept SharedString methods by the wrapper\", async () => {\n\t\t\tconst sharedStringWithInterception = createSharedStringWithInterception(\n\t\t\t\tsharedString,\n\t\t\t\tdataStoreContext,\n\t\t\t\tpropertyInterceptionCb,\n\t\t\t);\n\n\t\t\t// Insert text into shared string.\n\t\t\tlet text: string = \"123\";\n\t\t\tlet syleProps: PropertySet = { style: \"bold\" };\n\t\t\tsharedStringWithInterception.insertText(0, text, syleProps);\n\t\t\tverifyString(\n\t\t\t\tsharedStringWithInterception,\n\t\t\t\ttext,\n\t\t\t\t{ ...syleProps, ...userAttributes },\n\t\t\t\t2,\n\t\t\t);\n\n\t\t\t// Replace text in the shared string.\n\t\t\ttext = \"aaa\";\n\t\t\tsyleProps = { style: \"italics \" };\n\t\t\tsharedStringWithInterception.replaceText(2, 3, \"aaa\", syleProps);\n\t\t\tverifyString(\n\t\t\t\tsharedStringWithInterception,\n\t\t\t\t\"12aaa\",\n\t\t\t\t{ ...syleProps, ...userAttributes },\n\t\t\t\t2,\n\t\t\t);\n\n\t\t\t// Annotate the shared string.\n\t\t\tconst colorProps = { color: \"green\" };\n\t\t\tsharedStringWithInterception.annotateRange(0, 5, colorProps);\n\t\t\tverifyString(\n\t\t\t\tsharedStringWithInterception,\n\t\t\t\t\"12aaa\",\n\t\t\t\t{ ...syleProps, ...colorProps, ...userAttributes },\n\t\t\t\t2,\n\t\t\t);\n\t\t});\n\n\t\tit(\"should be able to see changes made by the wrapper from the underlying shared string\", async () => {\n\t\t\tconst sharedStringWithInterception = createSharedStringWithInterception(\n\t\t\t\tsharedString,\n\t\t\t\tdataStoreContext,\n\t\t\t\tpropertyInterceptionCb,\n\t\t\t);\n\n\t\t\t// Insert text via the shared string with interception wrapper.\n\t\t\tlet text: string = \"123\";\n\t\t\tlet syleProps: PropertySet = { style: \"bold\" };\n\t\t\tsharedStringWithInterception.insertText(0, text, syleProps);\n\t\t\t// Verify the text and properties via the underlying shared string.\n\t\t\tverifyString(sharedString, text, { ...syleProps, ...userAttributes }, 2);\n\n\t\t\t// Replace text via the shared string with interception wrapper.\n\t\t\ttext = \"aaa\";\n\t\t\tsyleProps = { style: \"italics \" };\n\t\t\tsharedStringWithInterception.replaceText(2, 3, \"aaa\", syleProps);\n\t\t\t// Verify the text and properties via the underlying shared string.\n\t\t\tverifyString(sharedString, \"12aaa\", { ...syleProps, ...userAttributes }, 2);\n\n\t\t\t// Annotate the shared string.\n\t\t\tconst colorProps = { color: \"green\" };\n\t\t\tsharedStringWithInterception.annotateRange(0, 5, colorProps);\n\t\t\t// Verify the text and properties via the underlying shared string.\n\t\t\tverifyString(\n\t\t\t\tsharedString,\n\t\t\t\t\"12aaa\",\n\t\t\t\t{ ...syleProps, ...colorProps, ...userAttributes },\n\t\t\t\t2,\n\t\t\t);\n\t\t});\n\n\t\tit(\"should be able to see changes made by the underlying shared string from the wrapper\", async () => {\n\t\t\tconst sharedStringWithInterception = createSharedStringWithInterception(\n\t\t\t\tsharedString,\n\t\t\t\tdataStoreContext,\n\t\t\t\tpropertyInterceptionCb,\n\t\t\t);\n\n\t\t\t// Insert text via the underlying shared string.\n\t\t\tlet text: string = \"123\";\n\t\t\tlet syleProps: PropertySet = { style: \"bold\" };\n\t\t\tsharedString.insertText(0, text, syleProps);\n\t\t\t// Verify the text and properties via the interception wrapper. It should not have the user attributes.\n\t\t\tverifyString(sharedStringWithInterception, text, syleProps, 2);\n\n\t\t\t// Replace text via the underlying shared string.\n\t\t\ttext = \"aaa\";\n\t\t\tsyleProps = { style: \"italics \" };\n\t\t\tsharedString.replaceText(2, 3, \"aaa\", syleProps);\n\t\t\t// Verify the text and properties via the interception wrapper. It should not have the user attributes.\n\t\t\tverifyString(sharedStringWithInterception, \"12aaa\", syleProps, 2);\n\n\t\t\t// Annotate the shared string via the underlying shared string.\n\t\t\tconst colorProps = { color: \"green\" };\n\t\t\tsharedString.annotateRange(0, 5, colorProps);\n\t\t\t// Verify the text and properties via the interception wrapper. It should not have the user attributes.\n\t\t\tverifyString(sharedStringWithInterception, \"12aaa\", { ...syleProps, ...colorProps }, 2);\n\t\t});\n\n\t\t/**\n\t\t * This test calls a method on the wrapper from the interception callback which will cause an infinite\n\t\t * recursion. Verify that the wrapper detects this and asserts.\n\t\t * Also, verify that the object is not unusable after the assert.\n\t\t */\n\t\tit(\"should assert if a wrapper method is called from the callback causing infinite recursion\", async () => {\n\t\t\t// eslint-disable-next-line prefer-const\n\t\t\tlet sharedStringWithInterception: SharedString;\n\n\t\t\tconst propsInRecursiveCb = { fromRecursiveCb: \"true\" };\n\t\t\tlet useWrapper: boolean = true;\n\t\t\t// If useWrapper above is true, this interception callback calls a method on the wrapped object\n\t\t\t// causing an infinite recursion.\n\t\t\t// If useWrapper is false, it uses the passed shared string which does not cause recursion.\n\t\t\tfunction recursiveInterceptionCb(properties?: PropertySet) {\n\t\t\t\tconst ss = useWrapper ? sharedStringWithInterception : sharedString;\n\t\t\t\tss.annotateRange(0, 1, propsInRecursiveCb);\n\t\t\t\treturn { ...properties, ...userAttributes };\n\t\t\t}\n\n\t\t\t// Create the interception wrapper with the above callback. The set method should throw an assertion as this\n\t\t\t// will cause infinite recursion.\n\t\t\tsharedStringWithInterception = createSharedStringWithInterception(\n\t\t\t\tsharedString,\n\t\t\t\tdataStoreContext,\n\t\t\t\trecursiveInterceptionCb,\n\t\t\t);\n\n\t\t\tlet text: string = \"123\";\n\t\t\tconst props: PropertySet = { style: \"bold\" };\n\t\t\t// First, insert text via the unwrapped shared string so that we have something to annotate in the\n\t\t\t// recursiveInterceptionCb.\n\t\t\tsharedString.insertText(0, text, props);\n\n\t\t\tlet asserted: boolean = false;\n\t\t\ttry {\n\t\t\t\ttext = \"abc\";\n\t\t\t\t// Try to replace text.\n\t\t\t\tsharedStringWithInterception.replaceText(1, 2, text);\n\t\t\t} catch (error: any) {\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\terror.message,\n\t\t\t\t\t\"0x0c8\",\n\t\t\t\t\t\"We should have caught an assert in replaceText because it detects an infinite recursion\",\n\t\t\t\t);\n\t\t\t\tasserted = true;\n\t\t\t}\n\t\t\tassert.equal(\n\t\t\t\tasserted,\n\t\t\t\ttrue,\n\t\t\t\t\"replaceText should have asserted because it detects inifinite recursion\",\n\t\t\t);\n\n\t\t\t// Verify that the object is still usable:\n\t\t\t// Set useWrapper to false and call replacetext on the wrapper again. Verify that we do not get an assert.\n\t\t\tuseWrapper = false;\n\t\t\ttext = \"test\";\n\t\t\tsharedStringWithInterception.replaceText(2, 3, text, props);\n\t\t\tverifyString(\n\t\t\t\tsharedStringWithInterception,\n\t\t\t\t\"12test\",\n\t\t\t\t{ ...props, ...userAttributes },\n\t\t\t\t2,\n\t\t\t);\n\n\t\t\t// Verify that the annotate on position 0 in the recursiveInterceptionCb annotated the attributes.\n\t\t\tverifyString(\n\t\t\t\tsharedStringWithInterception,\n\t\t\t\t\"12test\",\n\t\t\t\t{ ...props, ...propsInRecursiveCb },\n\t\t\t\t0,\n\t\t\t);\n\t\t});\n\t});\n});\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluid-experimental/dds-interceptions",
|
|
3
|
+
"version": "2.0.0-dev-rc.2.0.0.245554",
|
|
4
|
+
"description": "Distributed Data Structures that support an interception callback",
|
|
5
|
+
"homepage": "https://fluidframework.com",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/microsoft/FluidFramework.git",
|
|
9
|
+
"directory": "packages/framework/dds-interceptions"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "Microsoft and contributors",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": {
|
|
18
|
+
"types": "./lib/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"require": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"./public": {
|
|
27
|
+
"import": {
|
|
28
|
+
"types": "./lib/dds-interceptions-public.d.ts",
|
|
29
|
+
"default": "./lib/index.js"
|
|
30
|
+
},
|
|
31
|
+
"require": {
|
|
32
|
+
"types": "./dist/dds-interceptions-public.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"./internal": {
|
|
37
|
+
"import": {
|
|
38
|
+
"types": "./lib/index.d.ts",
|
|
39
|
+
"default": "./lib/index.js"
|
|
40
|
+
},
|
|
41
|
+
"require": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"default": "./dist/index.js"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"main": "dist/index.js",
|
|
48
|
+
"types": "dist/index.d.ts",
|
|
49
|
+
"c8": {
|
|
50
|
+
"all": true,
|
|
51
|
+
"cache-dir": "nyc/.cache",
|
|
52
|
+
"exclude": [
|
|
53
|
+
"src/test/**/*.*ts",
|
|
54
|
+
"dist/test/**/*.*js"
|
|
55
|
+
],
|
|
56
|
+
"exclude-after-remap": false,
|
|
57
|
+
"include": [
|
|
58
|
+
"src/**/*.*ts",
|
|
59
|
+
"dist/**/*.*js"
|
|
60
|
+
],
|
|
61
|
+
"report-dir": "nyc/report",
|
|
62
|
+
"reporter": [
|
|
63
|
+
"cobertura",
|
|
64
|
+
"html",
|
|
65
|
+
"text"
|
|
66
|
+
],
|
|
67
|
+
"temp-directory": "nyc/.nyc_output"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"@fluidframework/core-utils": "2.0.0-dev-rc.2.0.0.245554",
|
|
71
|
+
"@fluidframework/map": "2.0.0-dev-rc.2.0.0.245554",
|
|
72
|
+
"@fluidframework/merge-tree": "2.0.0-dev-rc.2.0.0.245554",
|
|
73
|
+
"@fluidframework/runtime-definitions": "2.0.0-dev-rc.2.0.0.245554",
|
|
74
|
+
"@fluidframework/sequence": "2.0.0-dev-rc.2.0.0.245554"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@arethetypeswrong/cli": "^0.13.3",
|
|
78
|
+
"@fluid-internal/mocha-test-setup": "2.0.0-dev-rc.2.0.0.245554",
|
|
79
|
+
"@fluid-tools/build-cli": "^0.34.0",
|
|
80
|
+
"@fluidframework/build-common": "^2.0.3",
|
|
81
|
+
"@fluidframework/build-tools": "^0.34.0",
|
|
82
|
+
"@fluidframework/eslint-config-fluid": "^5.1.0",
|
|
83
|
+
"@fluidframework/test-runtime-utils": "2.0.0-dev-rc.2.0.0.245554",
|
|
84
|
+
"@microsoft/api-extractor": "^7.42.3",
|
|
85
|
+
"@types/diff": "^3.5.1",
|
|
86
|
+
"@types/mocha": "^9.1.1",
|
|
87
|
+
"@types/node": "^18.19.0",
|
|
88
|
+
"c8": "^8.0.1",
|
|
89
|
+
"copyfiles": "^2.4.1",
|
|
90
|
+
"cross-env": "^7.0.3",
|
|
91
|
+
"diff": "^3.5.0",
|
|
92
|
+
"eslint": "~8.55.0",
|
|
93
|
+
"mocha": "^10.2.0",
|
|
94
|
+
"mocha-json-output-reporter": "^2.0.1",
|
|
95
|
+
"mocha-multi-reporters": "^1.5.1",
|
|
96
|
+
"moment": "^2.21.0",
|
|
97
|
+
"prettier": "~3.0.3",
|
|
98
|
+
"rimraf": "^4.4.0",
|
|
99
|
+
"typescript": "~5.1.6"
|
|
100
|
+
},
|
|
101
|
+
"fluidBuild": {
|
|
102
|
+
"tasks": {
|
|
103
|
+
"build:docs": {
|
|
104
|
+
"dependsOn": [
|
|
105
|
+
"...",
|
|
106
|
+
"api-extractor:commonjs",
|
|
107
|
+
"api-extractor:esnext"
|
|
108
|
+
],
|
|
109
|
+
"script": false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"typeValidation": {
|
|
114
|
+
"disabled": true,
|
|
115
|
+
"broken": {}
|
|
116
|
+
},
|
|
117
|
+
"scripts": {
|
|
118
|
+
"api": "fluid-build . --task api",
|
|
119
|
+
"api-extractor:commonjs": "api-extractor run --config ./api-extractor-cjs.json",
|
|
120
|
+
"api-extractor:esnext": "api-extractor run --local",
|
|
121
|
+
"build": "fluid-build . --task build",
|
|
122
|
+
"build:commonjs": "fluid-build . --task commonjs",
|
|
123
|
+
"build:compile": "fluid-build . --task compile",
|
|
124
|
+
"build:compile:min": "npm run build:compile",
|
|
125
|
+
"build:docs": "fluid-build . --task api",
|
|
126
|
+
"build:esnext": "tsc --project ./tsconfig.json",
|
|
127
|
+
"build:test": "npm run build:test:cjs && npm run build:test:esm",
|
|
128
|
+
"build:test:cjs": "fluid-tsc commonjs --project ./src/test/tsconfig.cjs.json",
|
|
129
|
+
"build:test:esm": "tsc --project ./src/test/tsconfig.json",
|
|
130
|
+
"check:are-the-types-wrong": "attw --pack . --entrypoints .",
|
|
131
|
+
"check:release-tags": "api-extractor run --local --config ./api-extractor-lint.json",
|
|
132
|
+
"ci:build:docs": "api-extractor run",
|
|
133
|
+
"clean": "rimraf --glob dist lib \"**/*.tsbuildinfo\" \"**/*.build.log\" _api-extractor-temp nyc",
|
|
134
|
+
"eslint": "eslint --format stylish src",
|
|
135
|
+
"eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
|
|
136
|
+
"format": "npm run prettier:fix",
|
|
137
|
+
"lint": "npm run prettier && npm run check:release-tags && npm run eslint",
|
|
138
|
+
"lint:fix": "npm run prettier:fix && npm run eslint:fix",
|
|
139
|
+
"prettier": "prettier --check . --cache --ignore-path ../../../.prettierignore",
|
|
140
|
+
"prettier:fix": "prettier --write . --cache --ignore-path ../../../.prettierignore",
|
|
141
|
+
"test": "npm run test:mocha",
|
|
142
|
+
"test:coverage": "c8 npm test",
|
|
143
|
+
"test:mocha": "npm run test:mocha:esm && echo skipping cjs to avoid overhead - npm run test:mocha:cjs",
|
|
144
|
+
"test:mocha:cjs": "mocha --recursive \"dist/test/**/*.spec.*js\" --exit -r node_modules/@fluid-internal/mocha-test-setup",
|
|
145
|
+
"test:mocha:esm": "mocha --recursive \"lib/test/**/*.spec.*js\" --exit -r node_modules/@fluid-internal/mocha-test-setup",
|
|
146
|
+
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
|
|
147
|
+
"tsc": "fluid-tsc commonjs --project ./tsconfig.cjs.json && copyfiles -f ../../../common/build/build-common/src/cjs/package.json ./dist",
|
|
148
|
+
"typetests:gen": "fluid-type-test-generator",
|
|
149
|
+
"typetests:prepare": "flub typetests --dir . --reset --previous --normalize"
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { createDirectoryWithInterception, createSharedMapWithInterception } from "./map/index.js";
|
|
7
|
+
export { createSharedStringWithInterception } from "./sequence/index.js";
|
package/src/map/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { createDirectoryWithInterception } from "./sharedDirectoryWithInterception.js";
|
|
7
|
+
export { createSharedMapWithInterception } from "./sharedMapWithInterception.js";
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { assert } from "@fluidframework/core-utils";
|
|
7
|
+
import { IDirectory } from "@fluidframework/map";
|
|
8
|
+
import { IFluidDataStoreContext } from "@fluidframework/runtime-definitions";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* - Create a new object from the passed subDirectory.
|
|
12
|
+
*
|
|
13
|
+
* - Modify the set method to call the setInterceptionCallback before calling set on the underlying object.
|
|
14
|
+
*
|
|
15
|
+
* - The setInterceptionCallback and the call to the underlying object are wrapped around an orderSequentially
|
|
16
|
+
* call to batch any operations that might happen in the callback.
|
|
17
|
+
*
|
|
18
|
+
* - Modify the sub directory methods to create / return a wrapper object that in turn intercepts the set method and
|
|
19
|
+
* calls the setInterceptionCallback.
|
|
20
|
+
*
|
|
21
|
+
* - When a sub directory is created from this directory, this base directory object is passed to it which is passed
|
|
22
|
+
* into the interception callback.
|
|
23
|
+
*
|
|
24
|
+
* @param baseDirectory - The base directory in the directory structure that is passed to the interception callback
|
|
25
|
+
* @param subDirectory - The underlying object that is to be intercepted
|
|
26
|
+
* @param context - The IFluidDataStoreContext that will be used to call orderSequentially
|
|
27
|
+
* @param setInterceptionCallback - The interception callback to be called
|
|
28
|
+
*
|
|
29
|
+
* @returns A new sub directory that intercepts the set method and calls the setInterceptionCallback.
|
|
30
|
+
*/
|
|
31
|
+
function createSubDirectoryWithInterception<T extends IDirectory>(
|
|
32
|
+
baseDirectory: T,
|
|
33
|
+
subDirectory: T,
|
|
34
|
+
context: IFluidDataStoreContext,
|
|
35
|
+
setInterceptionCallback: (
|
|
36
|
+
baseDirectory: IDirectory,
|
|
37
|
+
subDirectory: IDirectory,
|
|
38
|
+
key: string,
|
|
39
|
+
value: any,
|
|
40
|
+
) => void,
|
|
41
|
+
): T {
|
|
42
|
+
const subDirectoryWithInterception = Object.create(subDirectory);
|
|
43
|
+
|
|
44
|
+
// executingCallback keeps track of whether set is called recursively from the setInterceptionCallback.
|
|
45
|
+
let executingCallback: boolean = false;
|
|
46
|
+
|
|
47
|
+
subDirectoryWithInterception.set = (key: string, value: any) => {
|
|
48
|
+
let directory;
|
|
49
|
+
// Set should not be called on the wrapped object from the interception callback as this will lead to
|
|
50
|
+
// infinite recursion.
|
|
51
|
+
assert(
|
|
52
|
+
executingCallback === false,
|
|
53
|
+
0x0bf /* "set called recursively from the interception callback" */,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
context.containerRuntime.orderSequentially(() => {
|
|
57
|
+
directory = subDirectory.set(key, value);
|
|
58
|
+
executingCallback = true;
|
|
59
|
+
try {
|
|
60
|
+
setInterceptionCallback(baseDirectory, subDirectory, key, value);
|
|
61
|
+
} finally {
|
|
62
|
+
executingCallback = false;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
66
|
+
return directory;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
subDirectoryWithInterception.createSubDirectory = (subdirName: string): IDirectory => {
|
|
70
|
+
const subSubDirectory = subDirectory.createSubDirectory(subdirName);
|
|
71
|
+
return createSubDirectoryWithInterception(
|
|
72
|
+
baseDirectory,
|
|
73
|
+
subSubDirectory,
|
|
74
|
+
context,
|
|
75
|
+
setInterceptionCallback,
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
subDirectoryWithInterception.getSubDirectory = (subdirName: string): IDirectory | undefined => {
|
|
80
|
+
const subSubDirectory = subDirectory.getSubDirectory(subdirName);
|
|
81
|
+
return subSubDirectory === undefined
|
|
82
|
+
? subSubDirectory
|
|
83
|
+
: createSubDirectoryWithInterception(
|
|
84
|
+
baseDirectory,
|
|
85
|
+
subSubDirectory,
|
|
86
|
+
context,
|
|
87
|
+
setInterceptionCallback,
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
subDirectoryWithInterception.subdirectories = (): IterableIterator<[string, IDirectory]> => {
|
|
92
|
+
const localDirectoriesIterator = subDirectory.subdirectories();
|
|
93
|
+
const iterator = {
|
|
94
|
+
next(): IteratorResult<[string, IDirectory]> {
|
|
95
|
+
const nextVal = localDirectoriesIterator.next();
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
97
|
+
if (nextVal.done) {
|
|
98
|
+
return { value: undefined, done: true };
|
|
99
|
+
} else {
|
|
100
|
+
// Wrap the stored subdirectory in the interception wrapper.
|
|
101
|
+
const subDir = createSubDirectoryWithInterception(
|
|
102
|
+
baseDirectory,
|
|
103
|
+
nextVal.value[1],
|
|
104
|
+
context,
|
|
105
|
+
setInterceptionCallback,
|
|
106
|
+
);
|
|
107
|
+
return { value: [nextVal.value[0], subDir], done: false };
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
[Symbol.iterator]() {
|
|
111
|
+
return this;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
return iterator;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
subDirectoryWithInterception.getWorkingDirectory = (
|
|
118
|
+
relativePath: string,
|
|
119
|
+
): IDirectory | undefined => {
|
|
120
|
+
const subSubDirectory = subDirectory.getWorkingDirectory(relativePath);
|
|
121
|
+
return subSubDirectory === undefined
|
|
122
|
+
? subSubDirectory
|
|
123
|
+
: createSubDirectoryWithInterception(
|
|
124
|
+
baseDirectory,
|
|
125
|
+
subSubDirectory,
|
|
126
|
+
context,
|
|
127
|
+
setInterceptionCallback,
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
132
|
+
return subDirectoryWithInterception;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* - Create a new object from the passed IDirectory object.
|
|
137
|
+
*
|
|
138
|
+
* - Modify the set method to call the setInterceptionCallback before calling set on the underlying object.
|
|
139
|
+
*
|
|
140
|
+
* - The setInterceptionCallback and the call to the underlying object are wrapped around an orderSequentially
|
|
141
|
+
* call to batch any operations that might happen in the callback.
|
|
142
|
+
*
|
|
143
|
+
* - Modify the sub directory methods to create / return a wrapper object that in turn intercepts the set method and
|
|
144
|
+
* calls the setInterceptionCallback.
|
|
145
|
+
*
|
|
146
|
+
* - When a sub directory is created from this directory, this directory object is passed to it which is passed into
|
|
147
|
+
* the interception callback.
|
|
148
|
+
*
|
|
149
|
+
* @param baseDirectory - The underlying object that is to be intercepted
|
|
150
|
+
* @param context - The IFluidDataStoreContext that will be used to call orderSequentially
|
|
151
|
+
* @param setInterceptionCallback - The interception callback to be called
|
|
152
|
+
*
|
|
153
|
+
* @returns A new IDirectory object that intercepts the set method and calls the setInterceptionCallback.
|
|
154
|
+
* @internal
|
|
155
|
+
*/
|
|
156
|
+
export function createDirectoryWithInterception<T extends IDirectory>(
|
|
157
|
+
baseDirectory: T,
|
|
158
|
+
context: IFluidDataStoreContext,
|
|
159
|
+
setInterceptionCallback: (
|
|
160
|
+
baseDirectory: IDirectory,
|
|
161
|
+
subDirectory: IDirectory,
|
|
162
|
+
key: string,
|
|
163
|
+
value: any,
|
|
164
|
+
) => void,
|
|
165
|
+
): T {
|
|
166
|
+
return createSubDirectoryWithInterception(
|
|
167
|
+
baseDirectory,
|
|
168
|
+
baseDirectory,
|
|
169
|
+
context,
|
|
170
|
+
setInterceptionCallback,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { assert } from "@fluidframework/core-utils";
|
|
7
|
+
import { ISharedMap } from "@fluidframework/map";
|
|
8
|
+
import { IFluidDataStoreContext } from "@fluidframework/runtime-definitions";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* - Create a new object from the passed SharedMap.
|
|
12
|
+
*
|
|
13
|
+
* - Modify the set method to call the setInterceptionCallback before calling set on the underlying SharedMap.
|
|
14
|
+
*
|
|
15
|
+
* - The setInterceptionCallback and the call to the underlying SharedMap are wrapped around an
|
|
16
|
+
* orderSequentially call to batch any operations that might happen in the callback.
|
|
17
|
+
*
|
|
18
|
+
* @param sharedMap - The underlying SharedMap
|
|
19
|
+
* @param context - The IFluidDataStoreContext that will be used to call orderSequentially
|
|
20
|
+
* @param setInterceptionCallback - The interception callback to be called
|
|
21
|
+
*
|
|
22
|
+
* @returns A new SharedMap that intercepts the set method and calls the setInterceptionCallback.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export function createSharedMapWithInterception(
|
|
26
|
+
sharedMap: ISharedMap,
|
|
27
|
+
context: IFluidDataStoreContext,
|
|
28
|
+
setInterceptionCallback: (sharedMap: ISharedMap, key: string, value: any) => void,
|
|
29
|
+
): ISharedMap {
|
|
30
|
+
const sharedMapWithInterception = Object.create(sharedMap);
|
|
31
|
+
|
|
32
|
+
// executingCallback keeps track of whether set is called recursively from the setInterceptionCallback.
|
|
33
|
+
let executingCallback: boolean = false;
|
|
34
|
+
|
|
35
|
+
sharedMapWithInterception.set = (key: string, value: any) => {
|
|
36
|
+
let map;
|
|
37
|
+
// Set should not be called on the wrapped object from the interception callback as this will lead to
|
|
38
|
+
// infinite recursion.
|
|
39
|
+
assert(
|
|
40
|
+
executingCallback === false,
|
|
41
|
+
0x0c0 /* "set called recursively from the interception callback" */,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
context.containerRuntime.orderSequentially(() => {
|
|
45
|
+
map = sharedMap.set(key, value);
|
|
46
|
+
executingCallback = true;
|
|
47
|
+
try {
|
|
48
|
+
setInterceptionCallback(sharedMap, key, value);
|
|
49
|
+
} finally {
|
|
50
|
+
executingCallback = false;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
54
|
+
return map;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return sharedMapWithInterception as ISharedMap;
|
|
58
|
+
}
|